commit d348f04159882aa34a94c39ffc8babfa4c8eaa01 Author: faush01 Date: Sun Sep 28 10:30:07 2014 +1000 new addon based on the XBMC addon diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..1c9b0bd --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,283 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS +------------------------------------------------------------------------- +------------------------------------------------------------------------- diff --git a/addon.xml b/addon.xml new file mode 100644 index 0000000..3cead9f --- /dev/null +++ b/addon.xml @@ -0,0 +1,27 @@ + + + + + + + + + executable video audio image + + + + + all + en + GNU GENERAL PUBLIC LICENSE. Version 2, June 1991 + http://mediabrowser.tv/community/index.php?/forum/99-xbmb3c/ + http://mediabrowser.tv/ + https://github.com/MediaBrowser/MediaBrowser.XBMC/ + Browse and play local video, music and photo media file managed by the Media Browser Server + Browse and play local video, music and photo media file managed by the Media Browser Server + + diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..94782bb --- /dev/null +++ b/changelog.txt @@ -0,0 +1,163 @@ +0.9.6 + Features: + - Added MB3 Channels + - Added custom Item Info dialog + - Added a lot of new variables for use by skinners + - Changed behaviour of 'auto enter single season' - faster performance + - Added cache for 'All Movies' node. + - Added new 'search' dialog + - Added option to use 'poster' indicators for watched/in progress etc. + - Added option to sort 'Next Up' by show name. + - Option to disable CoverArt + - Various performance enhancements + - Added transcoding functionality + - Dutch language support + - Added unwatches movies/episodes nodes + - Added Genres, Studio, Actor nodes + - Added SuggestedItems nodes + - Partial implementation of new MB3 security + - Added flatten seasons option + - Support MusicVideos/HomeVideos in Recently Added + - Backdrops for user defines collections + - Support for 'media stubs' (physical media etc). + Bug fixes: + - Fix long XBMC close times + - Fixed incorrectly setting 'watched' under certain conditions + - Fixed rotating art for episodes + - Fixed percentage complete calculation + - More ArtWork fixes + - Multiple 'NoneType' fixes + - Don't offer 'Play from here' on folders + - Fix 'ItemInfo' 'Play' button (requires skin mod) + - Consider 'SortName' for collections + +0.9.5 + - Auto server discovery + - Cast Images + - Actor cross-referencing + - Additional artwork and metadata during playback + - Rotating backdrops for individual items + - Default views settings (must be supported by the skin) + - TV Theme music support + - Option to disable background services + - Proper artwork support at the Series/Season/Episode levels + - New method for skin widget support + - Proper sorting for Series and BoxSets + - Present episode counts and watch/unwatched counts + - Removed skin_diffs folder - use XBMB3C skin repo instead + +0.9.0 + - Search capability + - Better skin integration + - Added Favorite Shows node + - Update NextUp widget on stop + - Check that service is running + - Error handling for non-UNC paths + - Photo widget support + - Better MB3->XBMC art mapping + - Meta-data for playback in-progress items + - More granular debug logs + - No meta-data cache for less than 25 items (helps with TV status updates) + +0.8.5 + - Added remote control from other clients (null_pointer) + - Added trailer support (im85288) + - Added Couch Potato trailer integration (im85288) + - Updated to support server security update (xnappo) + - Server path substitution support (im85288) + - Added premier date and airtime to Upcoming TV (im85288) + - Added in-progress Movie and Episode entry points (im85288) + - Added percent text to in-progress items (null_pointer) + - Aeon Nox widget mods (Recently Added Moves/Episodes, NextUp Episodes) (xnappo) + - Added offer delete on episode played option (xnappo) + - Added optional progress dialog for large collections (null_pointer) + - Various improvements to data presentation (all) + +0.8.0 - Improved cache accuracy (null_pointer) + - Added Confluence auto-menu creation + (add movies, then TV, then others to favorites, relaunch) (null_pointer) + - Added hooks for xperience1080++ automation. Gotham only! (im85288) + - Added much more art (disc art, clear art banner art etc). Gotham only! (im85288) + - Added total play time for boxsets (null_pointer) + - Provide skins boxset information (im85288) + - Added rotating background fanart (null_pointer/im85288) + - Use GZIP for JSON requests (null_pointer) + - Added configurable options for played and resume times/percentages (null_pointer) + - Added extra information for RecentMovies/Episodes (im85288) + - Added RecentAlbums, RandomMovies, RandomEpisodes, RandomAlbums, NextUpTV services (im85288) + - Provide runtime and other information in list view (xnappo) + - Added 'Studio' metadata (xnappo) + - Added 'poster' art (xnappo) + - Added BoxSet video node (xnappo) + - Added trailers count, fixed movie totals (im85288) + +0.7.5 - Added simplejson/json switch + - Added simplejson as a requirement + - Changed to use 'Type' instead of 'DisplayMediaType' per Luke + - Added Confluence skin mods (null_pointer) + - Added recentmovie/recenttv list for use by skins (null_pointer) + - Bug fix in service to use data from settings + - Make using Series art for episodes an option + +0.7.0 - Switched all data from XML to JSON + - NOTE: If you have added nodes to your main menu, you will need to redo them + - Removed local image copying - new image proxy service by Null_Pointer! + - NOTE: You can delete the .png files in addon_data! + - Added local data cache (null_pointer) + - Changed 'Play All From Here' to start from current episode + - Fixed crash in latest episodes when a 'special' is present + - Fixed DVD playback + +0.6.5 - Added preliminary transcoding support + - Added preliminary music support (plays, no metadata yet) + - Fixed bug with non-ASCII characters in collection name + - Gracefully handle username not specified + - Fixed XML compliance issue for official repo submission + +0.6.0 - Added resume tracking + - Added playback from resume point (SMB only) + - Added support for multiple users + - Added password authentication + - Added SMB username/password option + - Added option to play from HTTP instead of SMB (note: resume does not work with this option) + - Added default sort modes + - Changed to not resolve real path until playback. Pi speedup? + - Fixed boxsets containing only one movie + - Removed xml caching - not needed (switched from httplib2 to requests) + - Cleaned up more for official repo submission requirements + +0.5.5 - Finished requirements for official repo submission + - Added localization + - Added 'Auto enter single folder items' option + - Added 'Play from here' + - Added Genre filter to context menu + - Added 'NextUp' menu entry + +0.5.0 - Added Sorting support via Context Menu + - Added Sort order support via Context Menu + - Fixed bug with unaired shows appearing in TV + - Fixed bug with certain characters causing errors in playback path + +0.4.5 - Added Recently Added Movies, TV + - Added Favorites support (excuse the trophy icon instead of heart, best I could do) + - Added Upcoming TV + - Added option to mark watched on play (still not progress tracking) + - Preparing for official repository submission (dos2unix lfs) + - Made context menu smarter + - Use Show art for Episodes (for now - MB3 episode artwork doesn't play well will XBMC skins) + - Changed cache to default to 0 (off) - this was needed only because of a FlexRaid issue on my system + +0.4.0 - Added section title + - Display correct list type for category + - Implemented context menus for delete/mark watched/mark unwatched. + - Added episode numbers + - Added cast info + +0.3.0 - Fixes boxsets + - Added meta-data + +0.2.0 - Added caching + - Removed more plex stuff + - XBMB3C-specific settings + +0.1.0 - Initial release diff --git a/default.py b/default.py new file mode 100644 index 0000000..e678d5b --- /dev/null +++ b/default.py @@ -0,0 +1,2897 @@ +''' + @document : default.py + @package : XBMB3C add-on + @authors : xnappo, null_pointer, im85288 + @copyleft : 2013, xnappo + + @license : Gnu General Public License - see LICENSE.TXT + @description: XBMB3C XBMC add-on + + This file is part of the XBMC XBMB3C Plugin. + + XBMB3C Plugin is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + XBMB3C Plugin is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with XBMB3C Plugin. If not, see . + + Thanks to Hippojay for the PleXBMC plugin this is derived from + +''' + +import struct +import urllib +import glob +import re +import hashlib +import xbmcplugin +import xbmcgui +import xbmcaddon +import httplib +import socket +import sys +import os +import time +import inspect +import base64 +import random +import datetime +import requests +from urlparse import urlparse +import cProfile +import pstats +import threading +import hashlib +import StringIO +import gzip +import xml.etree.ElementTree as etree + +__settings__ = xbmcaddon.Addon(id='plugin.video.xbmb3c') +__cwd__ = __settings__.getAddonInfo('path') +__addon__ = xbmcaddon.Addon(id='plugin.video.xbmb3c') +__addondir__ = xbmc.translatePath( __addon__.getAddonInfo('profile') ) +__language__ = __addon__.getLocalizedString + +BASE_RESOURCE_PATH = xbmc.translatePath( os.path.join( __cwd__, 'resources', 'lib' ) ) +sys.path.append(BASE_RESOURCE_PATH) +PLUGINPATH = xbmc.translatePath( os.path.join( __cwd__) ) + +from DownloadUtils import DownloadUtils +from ItemInfo import ItemInfo +from Utils import PlayUtils +from ClientInformation import ClientInformation +from PersonInfo import PersonInfo +from SearchDialog import SearchDialog + +XBMB3C_VERSION = ClientInformation().getVersion() + +xbmc.log ("===== XBMB3C START =====") + +xbmc.log ("XBMB3C -> running Python: " + str(sys.version_info)) +xbmc.log ("XBMB3C -> running XBMB3C: " + str(XBMB3C_VERSION)) +xbmc.log (xbmc.getInfoLabel( "System.BuildVersion" )) + +#Get the setting from the appropriate file. +CP_ADD_URL = 'XBMC.RunPlugin(plugin://plugin.video.couchpotato_manager/movies/add?title=%s)' +_MODE_GETCONTENT=0 +_MODE_MOVIES=0 +_MODE_SEARCH=2 +_MODE_SETVIEWS=3 +_MODE_SHOW_SECTIONS=4 +_MODE_BASICPLAY=12 +_MODE_PLAYLISTPLAY=13 +_MODE_CAST_LIST=14 +_MODE_PERSON_DETAILS=15 +_MODE_WIDGET_CONTENT=16 +_MODE_ITEM_DETAILS=17 +_MODE_SHOW_SEARCH=18 +_MODE_SHOW_PARENT_CONTENT=21 + +#Check debug first... +logLevel = 0 +try: + logLevel = int(__settings__.getSetting('logLevel')) +except: + pass + +import json as json + +#define our global download utils +downloadUtils = DownloadUtils() + +def printDebug( msg, level = 1): + if(logLevel >= level): + if(logLevel == 2): + try: + xbmc.log("XBMB3C " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg)) + except UnicodeEncodeError: + xbmc.log("XBMB3C " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8'))) + else: + try: + xbmc.log("XBMB3C " + str(level) + " -> " + str(msg)) + except UnicodeEncodeError: + xbmc.log("XBMB3C " + str(level) + " -> " + str(msg.encode('utf-8'))) + + +def getAuthHeader(): + txt_mac = downloadUtils.getMachineId() + version = ClientInformation().getVersion() + userid = xbmcgui.Window( 10000 ).getProperty("userid") + deviceName = __settings__.getSetting('deviceName') + deviceName = deviceName.replace("\"", "_") + authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"XBMC\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" + headers = {'Accept-encoding': 'gzip', 'Authorization' : authString} + xbmc.log("XBMB3C Authentication Header : " + str(headers)) + return headers + +def getPlatform( ): + + if xbmc.getCondVisibility('system.platform.osx'): + return "OSX" + elif xbmc.getCondVisibility('system.platform.atv2'): + return "ATV2" + elif xbmc.getCondVisibility('system.platform.ios'): + return "iOS" + elif xbmc.getCondVisibility('system.platform.windows'): + return "Windows" + elif xbmc.getCondVisibility('system.platform.linux'): + return "Linux/RPi" + elif xbmc.getCondVisibility('system.platform.android'): + return "Linux/Android" + + return "Unknown" + +XBMB3C_PLATFORM=getPlatform() +xbmc.log ("XBMB3C -> Platform: " + str(XBMB3C_PLATFORM)) + +g_flatten = __settings__.getSetting('flatten') +printDebug("XBMB3C -> Flatten is: " + g_flatten) + +xbmc.log ("XBMB3C -> LogLevel: " + str(logLevel)) + +g_contextReplace=True + +g_loc = "special://home/addons/plugin.video.XBMB3C" + +#Create the standard header structure and load with a User Agent to ensure we get back a response. +g_txheaders = { + 'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US;rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729)', + } + +#Set up holding variable for session ID +global g_sessionID +g_sessionID=None + +genreList=[__language__(30069),__language__(30070),__language__(30071),__language__(30072),__language__(30073),__language__(30074),__language__(30075),__language__(30076),__language__(30077),__language__(30078),__language__(30079),__language__(30080),__language__(30081),__language__(30082),__language__(30083),__language__(30084),__language__(30085),__language__(30086),__language__(30087),__language__(30088),__language__(30089)] +sortbyList=[__language__(30059),__language__(30060),__language__(30061),__language__(30062),__language__(30063),__language__(30064),__language__(30065),__language__(30066),__language__(30067)] + +def getServerDetails(): + + printDebug("Getting Server Details from Network") + + MESSAGE = "who is MediaBrowserServer?" + #MULTI_GROUP = ("224.3.29.71", 7359) + #MULTI_GROUP = ("127.0.0.1", 7359) + MULTI_GROUP = ("", 7359) + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(6.0) + + #ttl = struct.pack('b', 20) + #sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) + + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) + sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) + + xbmc.log("MutliGroup : " + str(MULTI_GROUP)); + xbmc.log("Sending UDP Data : " + MESSAGE); + sock.sendto(MESSAGE, MULTI_GROUP) + + try: + data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes + xbmc.log("Received Response : " + data) + if(data[0:18] == "MediaBrowserServer"): + xbmc.log("Found Server : " + data[19:]) + return data[19:] + except: + xbmc.log("No UDP Response") + pass + + return None + +def getCollections(detailsString): + printDebug("== ENTER: getCollections ==") + + MB_server = __settings__.getSetting('ipaddress')+":"+__settings__.getSetting('port') + + userid = downloadUtils.getUserId() + + if(userid == None or len(userid) == 0): + return {} + + try: + jsonData = downloadUtils.downloadUrl(MB_server + "/mediabrowser/Users/" + userid + "/Items/Root?format=json") + except Exception, msg: + error = "Get connect : " + str(msg) + xbmc.log (error) + return {} + + printDebug("jsonData : " + jsonData, level=2) + result = json.loads(jsonData) + + parentid = result.get("Id") + printDebug("parentid : " + parentid) + + htmlpath = ("http://%s/mediabrowser/Users/" % MB_server) + jsonData = downloadUtils.downloadUrl(htmlpath + userid + "/items?ParentId=" + parentid + "&Sortby=SortName&format=json") + printDebug("jsonData : " + jsonData, level=2) + collections=[] + + if jsonData is False: + return {} + + result = json.loads(jsonData) + result = result.get("Items") + + for item in result: + if(item.get("RecursiveItemCount") != "0"): + Name =(item.get("Name")).encode('utf-8') + if __settings__.getSetting(urllib.quote('sortbyfor'+Name)) == '': + __settings__.setSetting(urllib.quote('sortbyfor'+Name),'SortName') + __settings__.setSetting(urllib.quote('sortorderfor'+Name),'Ascending') + + total = str(item.get("RecursiveItemCount")) + section = item.get("CollectionType") + if (section == None): + section = "movies" + collections.append( {'title' : Name, + 'address' : MB_server , + 'thumb' : downloadUtils.getArtwork(item,"Primary") , + 'fanart_image' : downloadUtils.getArtwork(item, "Backdrop") , + 'poster' : downloadUtils.getArtwork(item,"Primary") , + 'sectype' : section, + 'section' : section, + 'guiid' : item.get("Id"), + 'path' : ('/mediabrowser/Users/' + userid + '/items?ParentId=' + item.get("Id") + '&IsVirtualUnaired=false&IsMissing=False&Fields=' + detailsString + '&SortOrder='+__settings__.getSetting('sortorderfor'+urllib.quote(Name))+'&SortBy='+__settings__.getSetting('sortbyfor'+urllib.quote(Name))+'&Genres=&format=json'), + 'collapsed_path' : ('/mediabrowser/Users/' + userid + '/items?ParentId=' + item.get("Id") + '&IsVirtualUnaired=false&IsMissing=False&Fields=' + detailsString + '&SortOrder='+__settings__.getSetting('sortorderfor'+urllib.quote(Name))+'&SortBy='+__settings__.getSetting('sortbyfor'+urllib.quote(Name))+'&Genres=&format=json&CollapseBoxSetItems=true'), + 'recent_path' : ('/mediabrowser/Users/' + userid + '/items?ParentId=' + item.get("Id") + '&Limit=' + __settings__.getSetting("numRecentMovies") +'&Recursive=true&SortBy=DateCreated&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsNotFolder&ExcludeLocationTypes=Virtual&format=json'), + 'inprogress_path' : ('/mediabrowser/Users/' + userid + '/items?ParentId=' + item.get("Id") +'&Recursive=true&SortBy=DatePlayed&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsNotFolder,IsResumable&ExcludeLocationTypes=Virtual&format=json'), + 'genre_path' : ('/mediabrowser/Genres?Userid=' + userid + '&parentId=' + item.get("Id") +'&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&Recursive=true&format=json'), + 'nextepisodes_path' : ('/mediabrowser/Shows/NextUp/?Userid=' + userid + '&parentId=' + item.get("Id") +'&Recursive=true&SortBy=DateCreated&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsNotFolder,IsUnplayed&IsVirtualUnaired=false&IsMissing=False&ExcludeLocationTypes=Virtual&IncludeItemTypes=Episode&format=json'), + 'unwatched_path' : ('/mediabrowser/Users/' + userid + '/items?ParentId=' + item.get("Id") +'&Recursive=true&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&Filters=IsNotFolder,IsUnplayed&ExcludeLocationTypes=Virtual&format=json')}) + + printDebug("Title " + Name) + + # Add standard nodes + collections.append({'title':__language__(30170), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=' + detailsString + '&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Movie&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30171), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=' + detailsString + '&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Series&format=json','thumb':'', 'poster':'', 'fanart_image':'' , 'guiid':''}) + collections.append({'title':__language__(30172), 'sectype' : 'std.music', 'section' : 'music' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=' + detailsString + '&Recursive=true&SortOrder=Ascending&IncludeItemTypes=MusicArtist&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':'' }) + collections.append({'title':__language__(30173), 'sectype' : 'std.channels', 'section' : 'channels' , 'address' : MB_server , 'path' : '/mediabrowser/Channels?' + userid +'&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':'' }) + collections.append({'title':__language__(30174), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Limit=' + __settings__.getSetting("numRecentMovies") +'&Recursive=true&SortBy=DateCreated&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IncludeItemTypes=Movie&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30175), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Limit=' + __settings__.getSetting("numRecentTV") +'&Recursive=true&SortBy=DateCreated&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=Episode&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30176), 'sectype' : 'std.music', 'section' : 'music' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Limit=' + __settings__.getSetting("numRecentMusic") +'&Recursive=true&SortBy=DateCreated&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsUnplayed&IncludeItemTypes=MusicAlbum&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30177), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=DatePlayed&SortOrder=Descending&Fields=' + detailsString + '&Filters=IsResumable&IncludeItemTypes=Movie&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30178), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=DatePlayed&SortOrder=Descending&Fields=' + detailsString + '&Filters=IsResumable&IncludeItemTypes=Episode&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30179), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Shows/NextUp/?Userid=' + userid + '&Recursive=true&SortBy=DateCreated&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=Episode&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30180), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=sortName&Fields=' + detailsString + '&SortOrder=Ascending&Filters=IsFavorite,IsNotFolder&IncludeItemTypes=Movie&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30181), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=sortName&Fields=' + detailsString + '&SortOrder=Ascending&Filters=IsFavorite&IncludeItemTypes=Series&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30182), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=DateCreated&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsNotFolder,IsFavorite&IncludeItemTypes=Episode&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30183), 'sectype' : 'std.music', 'section' : 'music' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Limit=' + __settings__.getSetting("numRecentMusic") + '&Recursive=true&SortBy=PlayCount&Fields=' + detailsString + '&SortOrder=Descending&Filters=IsPlayed&IncludeItemTypes=MusicAlbum&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30184), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=PremiereDate&Fields=' + detailsString + '&SortOrder=Ascending&Filters=IsUnplayed&IsVirtualUnaired=true&IsNotFolder&IncludeItemTypes=Episode&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30185), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&IncludeItemTypes=BoxSet&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30186), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&IncludeItemTypes=Trailer&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30187), 'sectype' : 'std.music', 'section' : 'musicvideos' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&IncludeItemTypes=MusicVideo&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30188), 'sectype' : 'std.photo', 'section' : 'photos' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Recursive=true&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&IncludeItemTypes=Photo&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30189), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?SortBy=SortName&Fields=' + detailsString + '&Recursive=true&SortOrder=Ascending&Filters=IsUnplayed&IncludeItemTypes=Movie&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30190), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Genres?Userid=' + userid + '&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&Recursive=true&IncludeItemTypes=Movie&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30191), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Studios?Userid=' + userid + '&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&Recursive=true&IncludeItemTypes=Movie&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30192), 'sectype' : 'std.movies', 'section' : 'movies' , 'address' : MB_server , 'path' : '/mediabrowser/Persons?Userid=' + userid + '&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&Recursive=true&IncludeItemTypes=Movie&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30193), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?Limit=50&Recursive=true&SortBy=DatePlayed&SortOrder=Descending&Fields=' + detailsString + '&Filters=IsUnplayed&IncludeItemTypes=Episode&format=json','thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30194), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Genres?Userid=' + userid + '&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&Recursive=true&IncludeItemTypes=Series&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30195), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Studios?Userid=' + userid + '&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&Recursive=true&IncludeItemTypes=Series&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30196), 'sectype' : 'std.tvshows', 'section' : 'tvshows' , 'address' : MB_server , 'path' : '/mediabrowser/Persons?Userid=' + userid + '&SortBy=SortName&Fields=' + detailsString + '&SortOrder=Ascending&Recursive=true&IncludeItemTypes=Series&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30197), 'sectype' : 'std.playlists', 'section' : 'playlists' , 'address' : MB_server , 'path' : '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=' + detailsString + '&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Playlist&mediatype=video&format=json' ,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + + if xbmcVersionNum >= 13: + collections.append({'title':__language__(30198) , 'sectype' : 'std.search', 'section' : 'search' , 'address' : MB_server , 'path' : '/mediabrowser/Search/Hints?' + userid,'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + collections.append({'title':__language__(30199) , 'sectype' : 'std.setviews', 'section' : 'setviews' , 'address' : 'SETVIEWS', 'path': 'SETVIEWS', 'thumb':'', 'poster':'', 'fanart_image':'', 'guiid':''}) + + return collections + +def markWatched (url): + resp = requests.delete(url, data='', headers=getAuthHeader()) # mark unwatched first to reset any play position + resp = requests.post(url, data='', headers=getAuthHeader()) + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + +def markUnwatched (url): + resp = requests.delete(url, data='', headers=getAuthHeader()) + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + +def markFavorite (url): + resp = requests.post(url, data='', headers=getAuthHeader()) + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + +def unmarkFavorite (url): + resp = requests.delete(url, data='', headers=getAuthHeader()) + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + +def sortby (): + sortOptions=["", "SortName","ProductionYear,SortName","PremiereDate,SortName","DateCreated,SortName","CriticRating,SortName","CommunityRating,SortName","PlayCount,SortName","Budget,SortName"] + sortOptionsText=sortbyList + return_value=xbmcgui.Dialog().select(__language__(30068),sortOptionsText) + WINDOW = xbmcgui.Window( 10000 ) + __settings__.setSetting('sortbyfor'+urllib.quote(WINDOW.getProperty("heading")),sortOptions[return_value]+',SortName') + newurl=re.sub("SortBy.*?&","SortBy="+ sortOptions[return_value] + "&",WINDOW.getProperty("currenturl")) + WINDOW.setProperty("currenturl",newurl) + u=urllib.quote(newurl)+'&mode=0' + xbmc.executebuiltin("Container.Update(plugin://plugin.video.xbmb3c/?url="+u+",\"replace\")")#, WINDOW.getProperty('currenturl') + +def genrefilter (): + genreFilters=["","Action","Adventure","Animation","Crime","Comedy","Documentary","Drama","Fantasy","Foreign","History","Horror","Music","Musical","Mystery","Romance","Science%20Fiction","Short","Suspense","Thriller","Western"] + genreFiltersText=genreList#["None","Action","Adventure","Animation","Crime","Comedy","Documentary","Drama","Fantasy","Foreign","History","Horror","Music","Musical","Mystery","Romance","Science Fiction","Short","Suspense","Thriller","Western"] + return_value=xbmcgui.Dialog().select(__language__(30090),genreFiltersText) + newurl=re.sub("Genres.*?&","Genres="+ genreFilters[return_value] + "&",WINDOW.getProperty("currenturl")) + WINDOW.setProperty("currenturl",newurl) + u=urllib.quote(newurl)+'&mode=0' + xbmc.executebuiltin("Container.Update(plugin://plugin.video.xbmb3c/?url="+u+",\"replace\")")#, WINDOW.getProperty('currenturl') + +def playall (startId): + temp_list = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + temp_list.clear() + jsonData = downloadUtils.downloadUrl(WINDOW.getProperty("currenturl")) + result = json.loads(jsonData) + result = result.get("Items") + found=0 + for item in result: + if str(item.get('Id'))==startId: + found=1 + if found==1: + if(item.get('RecursiveItemCount')!=0): + u=item.get('Path') + if __settings__.getSetting('smbusername')=='': + u=u.replace("\\\\","smb://") + else: + u=u.replace("\\\\","smb://"+__settings__.getSetting('smbusername')+':'+__settings__.getSetting('smbpassword')+'@') + u=u.replace("\\","/") + temp_list.add(u) + xbmc.Player().play(temp_list) + #Set a loop to wait for positive confirmation of playback + count = 0 + while not xbmc.Player().isPlaying(): + printDebug( "Not playing yet...sleep for 2") + count = count + 2 + if count >= 20: + return + else: + time.sleep(2) + +def sortorder (): + WINDOW = xbmcgui.Window( 10000 ) + if(__settings__.getSetting('sortorderfor'+urllib.quote(WINDOW.getProperty("heading")))=="Ascending"): + __settings__.setSetting('sortorderfor'+urllib.quote(WINDOW.getProperty("heading")),'Descending') + newurl=re.sub("SortOrder.*?&","SortOrder=Descending&",WINDOW.getProperty("currenturl")) + else: + __settings__.setSetting('sortorderfor'+urllib.quote(WINDOW.getProperty("heading")),'Ascending') + newurl=re.sub("SortOrder.*?&","SortOrder=Ascending&",WINDOW.getProperty("currenturl")) + WINDOW.setProperty("currenturl",newurl) + u=urllib.quote(newurl)+'&mode=0' + xbmc.executebuiltin("Container.Update(plugin://plugin.video.xbmb3c/?url="+u+",\"replace\")")#, WINDOW.getProperty('currenturl') + + +def delete (url): + return_value = xbmcgui.Dialog().yesno(__language__(30091),__language__(30092)) + if return_value: + printDebug('Deleting via URL: ' + url) + progress = xbmcgui.DialogProgress() + progress.create(__language__(30052), __language__(30053)) + resp = requests.delete(url, data='', headers=getAuthHeader()) + deleteSleep=0 + while deleteSleep<10: + xbmc.sleep(1000) + deleteSleep=deleteSleep+1 + progress.update(deleteSleep*10,__language__(30053)) + progress.close() + xbmc.executebuiltin("Container.Refresh") + +def addGUIItem( url, details, extraData, folder=True ): + + url = url.encode('utf-8') + + printDebug("Adding GuiItem for [%s]" % details.get('title','Unknown'), level=2) + printDebug("Passed details: " + str(details), level=2) + printDebug("Passed extraData: " + str(extraData), level=2) + #printDebug("urladdgui:" + str(url)) + if details.get('title','') == '': + return + + if extraData.get('mode',None) is None: + mode="&mode=0" + else: + mode="&mode=%s" % extraData['mode'] + + # play or show info + selectAction = __settings__.getSetting('selectAction') + + #Create the URL to pass to the item + if 'mediabrowser/Videos' in url: + if(selectAction == "1"): + u = sys.argv[0] + "?id=" + extraData.get('id') + "&mode=" + str(_MODE_ITEM_DETAILS) + else: + u = sys.argv[0] + "?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + elif 'mediabrowser/Search' in url: + u = sys.argv[0]+"?url=" + url + '&mode=' + str(_MODE_SEARCH) + elif 'SETVIEWS' in url: + u = sys.argv[0]+"?url=" + url + '&mode=' + str(_MODE_SETVIEWS) + elif url.startswith('http') or url.startswith('file'): + u = sys.argv[0]+"?url="+urllib.quote(url)+mode + elif 'PLAYLIST' in url: + u = sys.argv[0]+"?url=" + url + '&mode=' + str(_MODE_PLAYLISTPLAY) + else: + if(selectAction == "1"): + u = sys.argv[0] + "?id=" + extraData.get('id') + "&mode=" + str(_MODE_ITEM_DETAILS) + else: + u = sys.argv[0]+"?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + + #Create the ListItem that will be displayed + thumbPath=str(extraData.get('thumb','')) + + addCounts = __settings__.getSetting('addCounts') == 'true' + + WINDOW = xbmcgui.Window( 10000 ) + if WINDOW.getProperty("addshowname") == "true": + if extraData.get('locationtype')== "Virtual": + listItemName = extraData.get('premieredate').decode("utf-8") + u" - " + details.get('SeriesName','').decode("utf-8") + u" - " + u"S" + details.get('season').decode("utf-8") + u"E" + details.get('title','Unknown').decode("utf-8") + if(addCounts and extraData.get("RecursiveItemCount") != None and extraData.get("RecursiveUnplayedItemCount") != None): + listItemName = listItemName + " (" + str(extraData.get("RecursiveItemCount") - extraData.get("RecursiveUnplayedItemCount")) + "/" + str(extraData.get("RecursiveItemCount")) + ")" + list = xbmcgui.ListItem(listItemName, iconImage=thumbPath, thumbnailImage=thumbPath) + else: + if details.get('season') == None: + season = '0' + else: + season = details.get('season') + listItemName = details.get('SeriesName','').decode("utf-8") + u" - " + u"S" + season + u"E" + details.get('title','Unknown').decode("utf-8") + if(addCounts and extraData.get("RecursiveItemCount") != None and extraData.get("RecursiveUnplayedItemCount") != None): + listItemName = listItemName + " (" + str(extraData.get("RecursiveItemCount") - extraData.get("RecursiveUnplayedItemCount")) + "/" + str(extraData.get("RecursiveItemCount")) + ")" + list = xbmcgui.ListItem(listItemName, iconImage=thumbPath, thumbnailImage=thumbPath) + else: + listItemName = details.get('title','Unknown') + if(addCounts and extraData.get("RecursiveItemCount") != None and extraData.get("RecursiveUnplayedItemCount") != None): + listItemName = listItemName + " (" + str(extraData.get("RecursiveItemCount") - extraData.get("RecursiveUnplayedItemCount")) + "/" + str(extraData.get("RecursiveItemCount")) + ")" + list = xbmcgui.ListItem(listItemName, iconImage=thumbPath, thumbnailImage=thumbPath) + printDebug("Setting thumbnail as " + thumbPath, level=2) + + # calculate percentage + cappedPercentage = None + if (extraData.get('resumetime') != None and int(extraData.get('resumetime')) > 0): + duration = float(extraData.get('duration')) + if(duration > 0): + resume = float(extraData.get('resumetime')) / 60.0 + percentage = int((resume / duration) * 100.0) + cappedPercentage = percentage - (percentage % 10) + if(cappedPercentage == 0): + cappedPercentage = 10 + if(cappedPercentage == 100): + cappedPercentage = 90 + list.setProperty("complete_percentage", str(cappedPercentage)) + + # add resume percentage text to titles + addResumePercent = __settings__.getSetting('addResumePercent') == 'true' + if (addResumePercent and details.get('title') != None and cappedPercentage != None): + details['title'] = details.get('title') + " (" + str(cappedPercentage) + "%)" + + #Set the properties of the item, such as summary, name, season, etc + #list.setInfo( type=extraData.get('type','Video'), infoLabels=details ) + + #For all end items + if ( not folder): + #list.setProperty('IsPlayable', 'true') + if extraData.get('type','video').lower() == "video": + list.setProperty('TotalTime', str(extraData.get('duration'))) + list.setProperty('ResumeTime', str(extraData.get('resumetime'))) + + artTypes=['poster', 'tvshow.poster', 'fanart_image', 'clearlogo', 'discart', 'banner', 'clearart', 'landscape', 'small_poster', 'medium_poster','small_fanartimage', 'medium_fanartimage', 'medium_landscape'] + + for artType in artTypes: + imagePath=str(extraData.get(artType,'')) + list=setArt(list,artType, imagePath) + printDebug( "Setting " + artType + " as " + imagePath, level=2) + + menuItems = addContextMenu(details, extraData, folder) + if(len(menuItems) > 0): + list.addContextMenuItems( menuItems, g_contextReplace ) + + # new way + videoInfoLabels = {} + + if(extraData.get('type') == None or extraData.get('type') == "Video"): + videoInfoLabels.update(details) + else: + list.setInfo( type = extraData.get('type','Video'), infoLabels = details ) + + videoInfoLabels["duration"] = extraData.get("duration") + videoInfoLabels["playcount"] = extraData.get("playcount") + if (extraData.get('favorite') == 'true'): + videoInfoLabels["top250"] = "1" + + videoInfoLabels["mpaa"] = extraData.get('mpaa') + videoInfoLabels["rating"] = extraData.get('rating') + videoInfoLabels["director"] = extraData.get('director') + videoInfoLabels["writer"] = extraData.get('writer') + videoInfoLabels["year"] = extraData.get('year') + videoInfoLabels["studio"] = extraData.get('studio') + videoInfoLabels["genre"] = extraData.get('genre') + + videoInfoLabels["episode"] = details.get('episode') + videoInfoLabels["season"] = details.get('season') + + list.setInfo('video', videoInfoLabels) + + list.addStreamInfo('video', {'duration': extraData.get('duration'), 'aspect': extraData.get('aspectratio'),'codec': extraData.get('videocodec'), 'width' : extraData.get('width'), 'height' : extraData.get('height')}) + list.addStreamInfo('audio', {'codec': extraData.get('audiocodec'),'channels': extraData.get('channels')}) + + list.setProperty('CriticRating', str(extraData.get('criticrating'))) + list.setProperty('ItemType', extraData.get('itemtype')) + if extraData.get('totaltime') != None: + list.setProperty('TotalTime', extraData.get('totaltime')) + if extraData.get('TotalSeasons')!=None: + list.setProperty('TotalSeasons',extraData.get('TotalSeasons')) + if extraData.get('TotalEpisodes')!=None: + list.setProperty('TotalEpisodes',extraData.get('TotalEpisodes')) + if extraData.get('WatchedEpisodes')!=None: + list.setProperty('WatchedEpisodes',extraData.get('WatchedEpisodes')) + if extraData.get('UnWatchedEpisodes')!=None: + list.setProperty('UnWatchedEpisodes',extraData.get('UnWatchedEpisodes')) + if extraData.get('NumEpisodes')!=None: + list.setProperty('NumEpisodes',extraData.get('NumEpisodes')) + + + pluginCastLink = "plugin://plugin.video.xbmb3c?mode=" + str(_MODE_CAST_LIST) + "&id=" + str(extraData.get('id')) + list.setProperty('CastPluginLink', pluginCastLink) + list.setProperty('ItemGUID', extraData.get('guiid')) + list.setProperty('id', extraData.get('id')) + list.setProperty('Video3DFormat', details.get('Video3DFormat')) + + return (u, list, folder) + +def addContextMenu(details, extraData, folder): + printDebug("Building Context Menus", level=2) + commands = [] + watched = extraData.get('watchedurl') + if watched != None: + scriptToRun = PLUGINPATH + "/default.py" + + pluginCastLink = "XBMC.Container.Update(plugin://plugin.video.xbmb3c?mode=" + str(_MODE_CAST_LIST) + "&id=" + str(extraData.get('id')) + ")" + commands.append(( __language__(30100), pluginCastLink)) + + if extraData.get("playcount") == "0": + argsToPass = 'markWatched,' + extraData.get('watchedurl') + commands.append(( __language__(30093), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + else: + argsToPass = 'markUnwatched,' + extraData.get('watchedurl') + commands.append(( __language__(30094), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + if extraData.get('favorite') != 'true': + argsToPass = 'markFavorite,' + extraData.get('favoriteurl') + commands.append(( __language__(30095), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + else: + argsToPass = 'unmarkFavorite,' + extraData.get('favoriteurl') + commands.append(( __language__(30096), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + argsToPass = 'sortby' + commands.append(( __language__(30097), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + if 'Ascending' in WINDOW.getProperty("currenturl"): + argsToPass = 'sortorder' + commands.append(( __language__(30098), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + else: + argsToPass = 'sortorder' + commands.append(( __language__(30099), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + argsToPass = 'genrefilter' + commands.append(( __language__(30040), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + if not folder: + argsToPass = 'playall,' + extraData.get('id') + commands.append(( __language__(30041), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + argsToPass = 'refresh' + commands.append(( __language__(30042), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + argsToPass = 'delete,' + extraData.get('deleteurl') + commands.append(( __language__(30043), "XBMC.RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + if extraData.get('itemtype') == 'Trailer': + commands.append(( __language__(30046),"XBMC.RunPlugin(%s)" % CP_ADD_URL % details.get('title'))) + + return(commands) + +def getDetailsString(): + detailsString = "EpisodeCount,SeasonCount,Path,Genres,Studios,CumulativeRunTimeTicks" + if(__settings__.getSetting('includeStreamInfo') == "true"): + detailsString += ",MediaStreams" + if(__settings__.getSetting('includePeople') == "true"): + detailsString += ",People" + if(__settings__.getSetting('includeOverview') == "true"): + detailsString += ",Overview" + return (detailsString) + +def displaySections( filter=None ): + printDebug("== ENTER: displaySections() ==") + xbmcplugin.setContent(pluginhandle, 'files') + + dirItems = [] + userid = downloadUtils.getUserId() + extraData = { 'fanart_image' : '' , + 'type' : "Video" , + 'thumb' : '' } + + # Add collections + detailsString=getDetailsString() + collections = getCollections(detailsString) + for collection in collections: + details = {'title' : collection.get('title', 'Unknown') } + path = collection['path'] + extraData['mode'] = _MODE_MOVIES + extraData['thumb'] = collection['thumb'] + extraData['poster'] = collection['poster'] + extraData['fanart_image'] = collection['fanart_image'] + extraData['guiid'] = collection['guiid'] + s_url = 'http://%s%s' % ( collection['address'], path) + printDebug("addGUIItem:" + str(s_url) + str(details) + str(extraData)) + dirItems.append(addGUIItem(s_url, details, extraData)) + + #All XML entries have been parsed and we are ready to allow the user to browse around. So end the screen listing. + xbmcplugin.addDirectoryItems(pluginhandle, dirItems) + xbmcplugin.endOfDirectory(pluginhandle,cacheToDisc=False) + +def skin( filter=None, shared=False ): + printDebug("== ENTER: skin() ==") + + checkServer() + + #Get the global host variable set in settings + WINDOW = xbmcgui.Window( 10000 ) + sectionCount=0 + usrMoviesCount=0 + usrMusicCount=0 + usrTVshowsCount=0 + stdMoviesCount=0 + stdTVshowsCount=0 + stdMusicCount=0 + stdPhotoCount=0 + stdChannelsCount=0 + stdPlaylistsCount=0 + stdSearchCount=0 + dirItems = [] + + allSections = getCollections(getDetailsString()) + + for section in allSections: + + details={'title' : section.get('title', 'Unknown') } + + extraData={ 'fanart_image' : '' , + 'type' : "Video" , + 'thumb' : '' , + 'token' : section.get('token',None) } + + mode=_MODE_MOVIES + window="VideoLibrary" + + extraData['mode']=mode + modeurl="&mode=0" + s_url='http://%s%s' % (section['address'], section['path']) + murl= "?url="+urllib.quote(s_url)+modeurl + searchurl = "?url="+urllib.quote(s_url)+"&mode=2" + + #Build that listing.. + total = section.get('total') + if (total == None): + total = 0 + WINDOW.setProperty("xbmb3c.%d.title" % (sectionCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.%d.path" % (sectionCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.%d.collapsed.path" % (sectionCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('collapsed_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.%d.type" % (sectionCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.%d.fanart" % (sectionCount) , section.get('fanart_image')) + WINDOW.setProperty("xbmb3c.%d.recent.path" % (sectionCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('recent_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.%d.unwatched.path" % (sectionCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('unwatched_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.%d.inprogress.path" % (sectionCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('inprogress_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.%d.genre.path" % (sectionCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('genre_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.%d.nextepisodes.path" % (sectionCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('nextepisodes_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.%d.total" % (sectionCount) , str(total)) + if section.get('sectype')=='movies': + WINDOW.setProperty("xbmb3c.usr.movies.%d.title" % (usrMoviesCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.usr.movies.%d.path" % (usrMoviesCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.usr.movies.%d.type" % (usrMoviesCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.usr.movies.%d.content" % (usrMoviesCount) , "plugin://plugin.video.xbmb3c/" + murl) + WINDOW.setProperty("xbmb3c.usr.movies.%d.recent.path" % (usrMoviesCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('recent_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.usr.movies.%d.unwatched.path" % (usrMoviesCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('unwatched_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.usr.movies.%d.inprogress.path" % (usrMoviesCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('inprogress_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.usr.movies.%d.genre.path" % (usrMoviesCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('genre_path', '')) + modeurl + ",return)") + printDebug("xbmb3c.usr.movies.%d.title" % (usrMoviesCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.usr.movies.%d.type" % (usrMoviesCount) + "section is:" + section.get('section')) + usrMoviesCount += 1 + elif section.get('sectype')=='tvshows': + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.title" % (usrTVshowsCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.path" % (usrTVshowsCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.type" % (usrTVshowsCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.content" % (usrTVshowsCount) , "plugin://plugin.video.xbmb3c/" + murl) + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.recent.path" % (usrTVshowsCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('recent_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.unwatched.path" % (usrTVshowsCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('unwatched_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.inprogress.path" % (usrTVshowsCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('inprogress_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.genre.path" % (usrTVshowsCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('genre_path', '')) + modeurl + ",return)") + WINDOW.setProperty("xbmb3c.usr.tvshows.%d.nextepisodes.path" % (usrTVshowsCount) , "ActivateWindow(" + window + ",plugin://plugin.video.xbmb3c/?url=http://" + urllib.quote(section['address'] + section.get('nextepisodes_path', '')) + modeurl + ",return)") + + printDebug("xbmb3c.usr.tvshows.%d.title" % (usrTVshowsCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.usr.tvshows.%d.type" % (usrTVshowsCount) + "section is:" + section.get('section')) + usrTVshowsCount +=1 + elif section.get('sectype')=='music': + WINDOW.setProperty("xbmb3c.usr.music.%d.title" % (usrMusicCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.usr.music.%d.path" % (usrMusicCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.usr.music.%d.type" % (usrMusicCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.usr.music.%d.content" % (usrMusicCount) , "plugin://plugin.video.xbmb3c/" + murl) + printDebug("xbmb3c.usr.music.%d.title" % (usrMusicCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.usr.music.%d.type" % (usrMusicCount) + "section is:" + section.get('section')) + usrMusicCount +=1 + elif section.get('sectype')=='std.movies': + WINDOW.setProperty("xbmb3c.std.movies.%d.title" % (stdMoviesCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.std.movies.%d.path" % (stdMoviesCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.std.movies.%d.type" % (stdMoviesCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.std.movies.%d.content" % (stdMoviesCount) , "plugin://plugin.video.xbmb3c/" + murl) + printDebug("xbmb3c.std.movies.%d.title" % (stdMoviesCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.std.movies.%d.type" % (stdMoviesCount) + "section is:" + section.get('section')) + stdMoviesCount +=1 + elif section.get('sectype')=='std.tvshows': + WINDOW.setProperty("xbmb3c.std.tvshows.%d.title" % (stdTVshowsCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.std.tvshows.%d.path" % (stdTVshowsCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.std.tvshows.%d.type" % (stdTVshowsCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.std.tvshows.%d.content" % (stdTVshowsCount) , "plugin://plugin.video.xbmb3c/" + murl) + printDebug("xbmb3c.std.tvshows.%d.title" % (stdTVshowsCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.std.tvshows.%d.type" % (stdTVshowsCount) + "section is:" + section.get('section')) + stdTVshowsCount +=1 + elif section.get('sectype')=='std.music': + WINDOW.setProperty("xbmb3c.std.music.%d.title" % (stdMusicCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.std.music.%d.path" % (stdMusicCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.std.music.%d.type" % (stdMusicCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.std.music.%d.content" % (stdMusicCount) , "plugin://plugin.video.xbmb3c/" + murl) + printDebug("xbmb3c.std.music.%d.title" % (stdMusicCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.std.music.%d.type" % (stdMusicCount) + "section is:" + section.get('section')) + stdMusicCount +=1 + elif section.get('sectype')=='std.photo': + WINDOW.setProperty("xbmb3c.std.photo.%d.title" % (stdPhotoCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.std.photo.%d.path" % (stdPhotoCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.std.photo.%d.type" % (stdPhotoCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.std.photo.%d.content" % (stdPhotoCount) , "plugin://plugin.video.xbmb3c/" + murl) + printDebug("xbmb3c.std.photo.%d.title" % (stdPhotoCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.std.photo.%d.type" % (stdPhotoCount) + "section is:" + section.get('section')) + stdPhotoCount +=1 + elif section.get('sectype')=='std.channels': + WINDOW.setProperty("xbmb3c.std.channels.%d.title" % (stdChannelsCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.std.channels.%d.path" % (stdChannelsCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.std.channels.%d.type" % (stdChannelsCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.std.channels.%d.content" % (stdChannelsCount) , "plugin://plugin.video.xbmb3c/" + murl) + printDebug("xbmb3c.std.channels.%d.title" % (stdChannelsCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.std.channels.%d.type" % (stdChannelsCount) + "section is:" + section.get('section')) + stdChannelsCount +=1 + elif section.get('sectype')=='std.playlists': + WINDOW.setProperty("xbmb3c.std.playlists.%d.title" % (stdPlaylistsCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.std.playlists.%d.path" % (stdPlaylistsCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + WINDOW.setProperty("xbmb3c.std.playlists.%d.type" % (stdPlaylistsCount) , section.get('section')) + WINDOW.setProperty("xbmb3c.std.playlists.%d.content" % (stdPlaylistsCount) , "plugin://plugin.video.xbmb3c/" + murl) + printDebug("xbmb3c.std.playlists.%d.title" % (stdPlaylistsCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.std.playlists.%d.type" % (stdPlaylistsCount) + "section is:" + section.get('section')) + stdPlaylistsCount +=1 + elif section.get('sectype')=='std.search': + WINDOW.setProperty("xbmb3c.std.search.%d.title" % (stdSearchCount) , section.get('title', 'Unknown')) + WINDOW.setProperty("xbmb3c.std.search.%d.path" % (stdSearchCount) , "ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + searchurl+",return)") + WINDOW.setProperty("xbmb3c.std.search.%d.type" % (stdSearchCount) , section.get('section')) + printDebug("xbmb3c.std.search.%d.title" % (stdSearchCount) + "title is:" + section.get('title', 'Unknown')) + printDebug("xbmb3c.std.search.%d.type" % (stdSearchCount) + "section is:" + section.get('section')) + stdSearchCount +=1 #printDebug("Building window properties index [" + str(sectionCount) + "] which is [" + section.get('title').encode('utf-8') + " section - " + section.get('section') + " total - " + str(total) + "]") + printDebug("PATH in use is: ActivateWindow("+window+",plugin://plugin.video.xbmb3c/" + murl+",return)") + sectionCount += 1 + +def remove_html_tags( data ): + p = re.compile(r'<.*?>') + return p.sub('', data) + + +def PLAY( url, handle ): + printDebug("== ENTER: PLAY ==") + url=urllib.unquote(url) + + #server,id=url.split(',;') + urlParts = url.split(',;') + xbmc.log("PLAY ACTION URL PARTS : " + str(urlParts)) + server = urlParts[0] + id = urlParts[1] + autoResume = 0 + if(len(urlParts) > 2): + autoResume = int(urlParts[2]) + xbmc.log("PLAY ACTION URL AUTO RESUME : " + str(autoResume)) + + ip,port = server.split(':') + userid = downloadUtils.getUserId() + seekTime = 0 + resume = 0 + + id = urlParts[1] + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + id + "?format=json", suppress=False, popup=1 ) + result = json.loads(jsonData) + + if(autoResume != 0): + if(autoResume == -1): + resume_result = 1 + else: + resume_result = 0 + seekTime = (autoResume / 1000) / 10000 + else: + userData = result.get("UserData") + resume_result = 0 + + if userData.get("PlaybackPositionTicks") != 0: + reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000 + seekTime = reasonableTicks / 10000 + displayTime = str(datetime.timedelta(seconds=seekTime)) + display_list = [ "Resume from " + displayTime, "Start from beginning"] + resumeScreen = xbmcgui.Dialog() + resume_result = resumeScreen.select('Resume', display_list) + if resume_result == -1: + return + + + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + playlist.clear() + # check for any intros first + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + id + "/Intros?format=json", suppress=False, popup=1 ) + printDebug("Intros jsonData: " + jsonData) + result = json.loads(jsonData) + + # do not add intros when resume is invoked + if result.get("Items") != None and (seekTime == 0 or resume_result == 1): + for item in result.get("Items"): + id = item.get("Id") + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + id + "?format=json", suppress=False, popup=1 ) + result = json.loads(jsonData) + playurl = PlayUtils().getPlayUrl(server, id, result) + printDebug("Play URL: " + playurl) + thumbPath = downloadUtils.getArtwork(item, "Primary") + listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath) + setListItemProps(server, id, listItem, result) + + # Can not play virtual items + if (result.get("LocationType") == "Virtual") or (result.get("IsPlaceHolder") == True): + xbmcgui.Dialog().ok(__language__(30128), __language__(30129)) + return + + watchedurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id + positionurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayingItems/' + id + deleteurl = 'http://' + server + '/mediabrowser/Items/' + id + + # set the current playing info + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty(playurl+"watchedurl", watchedurl) + WINDOW.setProperty(playurl+"positionurl", positionurl) + WINDOW.setProperty(playurl+"deleteurl", "") + + WINDOW.setProperty(playurl+"runtimeticks", str(result.get("RunTimeTicks"))) + WINDOW.setProperty(playurl+"item_id", id) + + playlist.add(playurl, listItem) + + id = urlParts[1] + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + id + "?format=json", suppress=False, popup=1 ) + printDebug("Play jsonData: " + jsonData) + result = json.loads(jsonData) + + playurl = PlayUtils().getPlayUrl(server, id, result) + printDebug("Play URL: " + playurl) + thumbPath = downloadUtils.getArtwork(result, "Primary") + listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath) + setListItemProps(server, id, listItem, result) + + # Can not play virtual items + if (result.get("LocationType") == "Virtual"): + xbmcgui.Dialog().ok(__language__(30128), __language__(30129)) + return + + watchedurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id + positionurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayingItems/' + id + deleteurl = 'http://' + server + '/mediabrowser/Items/' + id + + # set the current playing info + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty(playurl+"watchedurl", watchedurl) + WINDOW.setProperty(playurl+"positionurl", positionurl) + WINDOW.setProperty(playurl+"deleteurl", "") + if result.get("Type")=="Episode" and __settings__.getSetting("offerDelete")=="true": + WINDOW.setProperty(playurl+"deleteurl", deleteurl) + + WINDOW.setProperty(playurl+"runtimeticks", str(result.get("RunTimeTicks"))) + WINDOW.setProperty(playurl+"item_id", id) + + playlist.add(playurl, listItem) + + xbmc.Player().play(playlist) + #Set a loop to wait for positive confirmation of playback + count = 0 + while not xbmc.Player().isPlaying(): + printDebug( "Not playing yet...sleep for 1 sec") + count = count + 1 + if count >= 10: + return + else: + time.sleep(1) + + if resume_result == 0: + jumpBackSec = int(__settings__.getSetting("resumeJumpBack")) + seekToTime = seekTime - jumpBackSec + while xbmc.Player().getTime() < (seekToTime - 5): + xbmc.Player().pause + xbmc.sleep(100) + xbmc.Player().seekTime(seekToTime) + xbmc.sleep(100) + xbmc.Player().play() + return + +def PLAYPlaylist( url, handle ): + printDebug("== ENTER: PLAY Playlist ==") + url=urllib.unquote(url) + + #server,id=url.split(',;') + urlParts = url.split(',;') + xbmc.log("PLAY Playlist ACTION URL PARTS : " + str(urlParts)) + server = urlParts[0] + id = urlParts[1] + ip,port = server.split(':') + userid = downloadUtils.getUserId() + seekTime = 0 + resume = 0 + + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Playlists/" + id + "/Items/?fields=path&format=json", suppress=False, popup=1 ) + printDebug("Playlist jsonData: " + jsonData) + result = json.loads(jsonData) + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + playlist.clear() + + for item in result.get("Items"): + id = item.get("Id") + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + id + "?format=json", suppress=False, popup=1 ) + result = json.loads(jsonData) + autoResume = 0 + playurl = PlayUtils().getPlayUrl(server, id, result) + printDebug("Play URL: " + playurl) + thumbPath = downloadUtils.getArtwork(item, "Primary") + listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath) + setListItemProps(server, id, listItem, result) + + # Can not play virtual items + if (result.get("LocationType") == "Virtual") or (result.get("IsPlaceHolder") == True): + xbmcgui.Dialog().ok(__language__(30128), __language__(30129)) + return + + watchedurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id + positionurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayingItems/' + id + deleteurl = 'http://' + server + '/mediabrowser/Items/' + id + + # set the current playing info + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty(playurl+"watchedurl", watchedurl) + WINDOW.setProperty(playurl+"positionurl", positionurl) + WINDOW.setProperty(playurl+"deleteurl", "") + if result.get("Type")=="Episode" and __settings__.getSetting("offerDelete")=="true": + WINDOW.setProperty(playurl+"deleteurl", deleteurl) + + WINDOW.setProperty(playurl+"runtimeticks", str(result.get("RunTimeTicks"))) + WINDOW.setProperty(playurl+"item_id", id) + + playlist.add(playurl, listItem) + + xbmc.Player().play(playlist) + printDebug( "Sent the following playlist url to the xbmc player: "+str(playurl)) + + #Set a loop to wait for positive confirmation of playback + count = 0 + while not xbmc.Player().isPlaying(): + printDebug( "Not playing playlist yet...sleep for 1 sec") + count = count + 1 + if count >= 10: + return + else: + time.sleep(1) + + if resume_result == 0: + jumpBackSec = int(__settings__.getSetting("resumeJumpBack")) + seekToTime = seekTime - jumpBackSec + while xbmc.Player().getTime() < (seekToTime - 5): + xbmc.Player().pause + xbmc.sleep(100) + xbmc.Player().seekTime(seekToTime) + xbmc.sleep(100) + xbmc.Player().play() + return + +def setListItemProps(server, id, listItem, result): + # set up item and item info + userid = downloadUtils.getUserId() + thumbID = id + eppNum = -1 + seasonNum = -1 + tvshowTitle = "" + if(result.get("Type") == "Episode"): + thumbID = result.get("SeriesId") + seasonNum = result.get("ParentIndexNumber") + eppNum = result.get("IndexNumber") + tvshowTitle = result.get("SeriesName") + seriesJsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + thumbID + "?format=json", suppress=False, popup=1 ) + seriesResult = json.loads(seriesJsonData) + resultForType=seriesResult + else: + resultForType = result + + setArt(listItem,'poster', downloadUtils.getArtwork(result, "Primary")) + setArt(listItem,'tvshow.poster', downloadUtils.getArtwork(result, "SeriesPrimary")) + setArt(listItem,'clearart', downloadUtils.getArtwork(result, "Art")) + setArt(listItem,'tvshow.clearart', downloadUtils.getArtwork(result, "Art")) + setArt(listItem,'clearlogo', downloadUtils.getArtwork(result, "Logo")) + setArt(listItem,'tvshow.clearlogo', downloadUtils.getArtwork(result, "Logo")) + setArt(listItem,'discart', downloadUtils.getArtwork(result, "Disc")) + setArt(listItem,'fanart_image', downloadUtils.getArtwork(result, "Backdrop")) + setArt(listItem,'landscape', downloadUtils.getArtwork(result, "Thumb")) + + listItem.setProperty('IsPlayable', 'true') + listItem.setProperty('IsFolder', 'false') + + studio = "" + studios = resultForType.get("Studios") + if(studios != None): + for studio_string in studios: + if studio=="": #Just take the first one + temp=studio_string.get("Name") + studio=temp.encode('utf-8') + listItem.setInfo('video', {'studio' : studio}) + + # play info + playinformation = '' + if PlayUtils().isDirectPlay(result) == True: + if __settings__.getSetting('playFromStream') == "true": + playinformation = __language__(30164) + else: + playinformation = __language__(30165) + else: + playinformation = __language__(30166) + details = { + 'title' : result.get("Name", "Missing Name") + ' - ' + playinformation, + 'plot' : result.get("Overview") + } + + if(eppNum > -1): + details["episode"] = str(eppNum) + + if(seasonNum > -1): + details["season"] = str(seasonNum) + + if tvshowTitle != None: + details["TVShowTitle"] = tvshowTitle + + listItem.setInfo( "Video", infoLabels=details ) + + # Process People + director='' + writer='' + people = result.get("People") + if(people != None): + for person in people: + if(person.get("Type") == "Director"): + director = director + person.get("Name") + ' ' + if(person.get("Type") == "Writing"): + writer = person.get("Name") + if(person.get("Type") == "Writer"): + writer = person.get("Name") + + # Process Genres + genre = "" + genres = result.get("Genres") + if(genres != None): + for genre_string in genres: + if genre == "": #Just take the first genre + genre = genre_string + else: + genre = genre + " / " + genre_string + + listItem.setInfo('video', {'director' : director}) + listItem.setInfo('video', {'writer' : writer}) + listItem.setInfo('video', {'mpaa': resultForType.get("OfficialRating")}) + listItem.setInfo('video', {'genre': genre}) + + return + +def get_params( paramstring ): + printDebug("Parameter string: " + paramstring, level=2) + param={} + if len(paramstring)>=2: + params=paramstring + + if params[0] == "?": + cleanedparams=params[1:] + else: + cleanedparams=params + + if (params[len(params)-1]=='/'): + params=params[0:len(params)-2] + + pairsofparams=cleanedparams.split('&') + for i in range(len(pairsofparams)): + splitparams={} + splitparams=pairsofparams[i].split('=') + if (len(splitparams))==2: + param[splitparams[0]]=splitparams[1] + elif (len(splitparams))==3: + param[splitparams[0]]=splitparams[1]+"="+splitparams[2] + printDebug("XBMB3C -> Detected parameters: " + str(param), level=2) + return param + +def getCacheValidator (server,url): + parsedserver,parsedport = server.split(':') + userid = downloadUtils.getUserId() + idAndOptions = url.split("ParentId=") + id = idAndOptions[1].split("&") + jsonData = downloadUtils.downloadUrl("http://"+server+"/mediabrowser/Users/" + userid + "/Items/" +id[0]+"?format=json", suppress=False, popup=1 ) + result = json.loads(jsonData) + userData = result.get("UserData") + printDebug ("RecursiveItemCount: " + str(result.get("RecursiveItemCount"))) + printDebug ("UnplayedItemCount: " + str(userData.get("UnplayedItemCount"))) + printDebug ("PlayedPercentage: " + str(userData.get("PlayedPercentage"))) + + playedPercentage = 0.0 + if(userData.get("PlayedPercentage") != None): + playedPercentage = userData.get("PlayedPercentage") + + playedTime = "{0:09.6f}".format(playedPercentage) + playedTime = playedTime.replace(".","-") + validatorString="" + if result.get("RecursiveItemCount") != None: + if int(result.get("RecursiveItemCount"))<=25: + validatorString='nocache' + else: + validatorString = str(result.get("RecursiveItemCount")) + "_" + str(userData.get("UnplayedItemCount")) + "_" + playedTime + printDebug ("getCacheValidator : " + validatorString) + return validatorString + +def getAllMoviesCacheValidator (server,url): + parsedserver,parsedport = server.split(':') + userid = downloadUtils.getUserId() + jsonData = downloadUtils.downloadUrl("http://"+server+"/mediabrowser/Users/" + userid + "/Views?format=json", suppress=False, popup=1 ) + alldata = json.loads(jsonData) + validatorString = "" + playedTime = "" + playedPercentage = 0.0 + + userData = {} + result=alldata.get("Items") + for item in result: + if item.get("Name")=="Movies": + userData = item.get("UserData") + printDebug ("RecursiveItemCount: " + str(item.get("RecursiveItemCount"))) + printDebug ("RecursiveUnplayedCount: " + str(userData.get("UnplayedItemCount"))) + printDebug ("RecursiveUnplayedCount: " + str(userData.get("PlayedPercentage"))) + + if(userData.get("PlayedPercentage") != None): + playedPercentage = userData.get("PlayedPercentage") + + playedTime = "{0:09.6f}".format(playedPercentage) + playedTime = playedTime.replace(".","-") + + if item.get("RecursiveItemCount") != None: + if int(item.get("RecursiveItemCount"))<=25: + validatorString='nocache' + else: + validatorString = "allmovies_" + str(item.get("RecursiveItemCount")) + "_" + str(userData.get("UnplayedItemCount")) + "_" + playedTime + printDebug ("getAllMoviesCacheValidator : " + validatorString) + return validatorString + +def getCacheValidatorFromData(result): + result = result.get("Items") + if(result == None): + result = [] + + itemCount = 0 + unwatchedItemCount = 0 + totalPlayedPercentage = 0 + for item in result: + userData = item.get("UserData") + if(userData != None): + if(item.get("IsFolder") == False): + itemCount = itemCount + 1 + if userData.get("Played") == False: + unwatchedItemCount = unwatchedItemCount + 1 + itemPossition = userData.get("PlaybackPositionTicks") + itemRuntime = item.get("RunTimeTicks") + if(itemRuntime != None and itemPossition != None): + itemPercent = float(itemPossition) / float(itemRuntime) + totalPlayedPercentage = totalPlayedPercentage + itemPercent + else: + totalPlayedPercentage = totalPlayedPercentage + 100 + else: + itemCount = itemCount + item.get("RecursiveItemCount") + unwatchedItemCount = unwatchedItemCount + userData.get("UnplayedItemCount") + PlayedPercentage=userData.get("PlayedPercentage") + if PlayedPercentage==None: + PlayedPercentage=0 + totalPlayedPercentage = totalPlayedPercentage + (item.get("RecursiveItemCount") * PlayedPercentage) + + if(itemCount == 0): + totalPlayedPercentage = 0.0 + else: + totalPlayedPercentage = totalPlayedPercentage / float(itemCount) + + playedTime = "{0:09.6f}".format(totalPlayedPercentage) + playedTime = playedTime.replace(".","-") + validatorString = "_" + str(itemCount) + "_" + str(unwatchedItemCount) + "_" + playedTime + printDebug ("getCacheValidatorFromData : " + validatorString) + return validatorString + +def getContent( url ): + ''' + This function takes the URL, gets the XML and determines what the content is + This XML is then redirected to the best processing function. + If a search term is detected, then show keyboard and run search query + @input: URL of XML page + @return: nothing, redirects to another function + ''' + global viewType + printDebug("== ENTER: getContent ==") + server=getServerFromURL(url) + lastbit=url.split('/')[-1] + printDebug("URL suffix: " + str(lastbit)) + printDebug("server: " + str(server)) + printDebug("URL: " + str(url)) + validator='nocache' #Don't cache special queries (recently added etc) + if "Parent" in url: + validator = "_" + getCacheValidator(server,url) + elif "&SortOrder=Ascending&IncludeItemTypes=Movie" in url: + validator = "_" + getAllMoviesCacheValidator(server,url) + + # ADD VALIDATOR TO FILENAME TO DETERMINE IF CACHE IS FRESH + + m = hashlib.md5() + m.update(url) + urlHash = m.hexdigest() + + jsonData = "" + cacheDataPath = __addondir__ + urlHash + validator + + if "NextUp" in url and __settings__.getSetting('sortNextUp') == "true": + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_TITLE) + else: + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_NONE) + result = None + + WINDOW = xbmcgui.Window( 10000 ) + force_data_reload = WINDOW.getProperty("force_data_reload") + WINDOW.setProperty("force_data_reload", "false") + + progress = None + if(__settings__.getSetting('showLoadProgress') == "true"): + progress = xbmcgui.DialogProgress() + progress.create(__language__(30121)) + progress.update(0, __language__(30122)) + + # if a cached file exists use it + # if one does not exist then load data from the url + if(os.path.exists(cacheDataPath)) and validator != 'nocache' and force_data_reload != "true": + cachedfie = open(cacheDataPath, 'r') + jsonData = cachedfie.read() + cachedfie.close() + printDebug("Data Read From Cache : " + cacheDataPath) + if(progress != None): + progress.update(0, __language__(30123)) + try: + result = loadJasonData(jsonData) + except: + printDebug("Json load failed from cache data") + result = [] + dataLen = len(result) + printDebug("Json Load Result : " + str(dataLen)) + if(dataLen == 0): + result = None + + # if there was no cache data for the cache data was not valid then try to load it again + if(result == None): + r = glob.glob(__addondir__ + urlHash + "*") + for i in r: + os.remove(i) + printDebug("No Cache Data, download data now") + if(progress != None): + progress.update(0, __language__(30124)) + jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1 ) + if(progress != None): + progress.update(0, __language__(30123)) + try: + result = loadJasonData(jsonData) + except: + xbmc.log("Json load failed from downloaded data") + result = [] + dataLen = len(result) + printDebug("Json Load Result : " + str(dataLen)) + if(dataLen > 0 and validator != 'nocache'): + cacheValidationString = getCacheValidatorFromData(result) + printDebug("getCacheValidator : " + validator) + printDebug("getCacheValidatorFromData : " + cacheValidationString) + if(validator == cacheValidationString): + printDebug("Validator String Match, Saving Cache Data") + cacheDataPath = __addondir__ + urlHash + cacheValidationString + printDebug("Saving data to cache : " + cacheDataPath) + cachedfie = open(cacheDataPath, 'w') + cachedfie.write(jsonData) + cachedfie.close() + elif("allmovies" in validator): + printDebug("All Movies Cache") + cacheDataPath = __addondir__ + urlHash + validator + printDebug("Saving data to cache : " + cacheDataPath) + cachedfie = open(cacheDataPath, 'w') + cachedfie.write(jsonData) + cachedfie.close() + if jsonData == "": + if(progress != None): + progress.close() + return + + printDebug("JSON DATA: " + str(result), level=2) + if "Search" in url: + dirItems = processSearch(url, result, progress) + elif "Channel" in url: + dirItems = processChannels(url, result, progress) + elif "&IncludeItemTypes=Playlist" in url: + dirItems = processPlaylists(url, result, progress) + elif "/mediabrowser/Genres?" in url and "&IncludeItemTypes=Movie" in url and "&parentId" not in url: + dirItems = processGenres(url, result, progress, "Movie") + elif "/mediabrowser/Genres?" in url and "&IncludeItemTypes=Series" in url and "&parentId" not in url: + dirItems = processGenres(url, result, progress, "Series") + elif "/mediabrowser/Genres?" in url and "&parentId" in url: + dirItems = processGenres(url, result, progress, "Movie") + elif "/mediabrowser/Studios?" in url and "&IncludeItemTypes=Movie" in url: + dirItems = processStudios(url, result, progress, "Movie") + elif "/mediabrowser/Studios?" in url and "&IncludeItemTypes=Series" in url: + dirItems = processStudios(url, result, progress, "Series") + elif "/mediabrowser/Persons?" in url and "&IncludeItemTypes=Movie" in url: + dirItems = processPeople(url, result, progress, "Movie") + elif "/mediabrowser/Persons?" in url and "&IncludeItemTypes=Series" in url: + dirItems = processPeople(url, result, progress, "Series") + else: + dirItems = processDirectory(url, result, progress) + xbmcplugin.addDirectoryItems(pluginhandle, dirItems) + + if("viewType" in globals()): + if __settings__.getSetting(xbmc.getSkinDir()+ '_VIEW' + viewType) != "": + xbmc.executebuiltin("Container.SetViewMode(%s)" % int(__settings__.getSetting(xbmc.getSkinDir()+ '_VIEW' + viewType))) + + xbmcplugin.endOfDirectory(pluginhandle, cacheToDisc=False) + + if(progress != None): + progress.update(100, __language__(30125)) + progress.close() + + return + +def loadJasonData(jsonData): + return json.loads(jsonData) + +def processDirectory(url, results, progress): + global viewType + cast=['None'] + printDebug("== ENTER: processDirectory ==") + parsed = urlparse(url) + parsedserver,parsedport=parsed.netloc.split(':') + userid = downloadUtils.getUserId() + printDebug("Processing secondary menus") + xbmcplugin.setContent(pluginhandle, 'movies') + + server = getServerFromURL(url) + setWindowHeading(url) + + detailsString = "Path,Genres,Studios,CumulativeRunTimeTicks" + if(__settings__.getSetting('includeStreamInfo') == "true"): + detailsString += ",MediaStreams" + if(__settings__.getSetting('includePeople') == "true"): + detailsString += ",People" + if(__settings__.getSetting('includeOverview') == "true"): + detailsString += ",Overview" + + dirItems = [] + result = results.get("Items") + if(result == None): + result = [] + if len(result) == 1 and __settings__.getSetting('autoEnterSingle') == "true": + if result[0].get("Type") == "Season": + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/items?ParentId=" + result[0].get("Id") + '&IsVirtualUnAired=false&IsMissing=false&Fields=' + detailsString + '&SortBy=SortName&format=json', suppress=False, popup=1 ) + results = json.loads(jsonData) + result=results.get("Items") + item_count = len(result) + current_item = 1; + + for item in result: + + if(progress != None): + percentDone = (float(current_item) / float(item_count)) * 100 + progress.update(int(percentDone), __language__(30126) + str(current_item)) + current_item = current_item + 1 + + if(item.get("Name") != None): + tempTitle = item.get("Name").encode('utf-8') + else: + tempTitle = "Missing Title" + + id = str(item.get("Id")).encode('utf-8') + guiid = id + isFolder = item.get("IsFolder") + + item_type = str(item.get("Type")).encode('utf-8') + + tempEpisode = "" + if (item.get("IndexNumber") != None): + episodeNum = item.get("IndexNumber") + if episodeNum < 10: + tempEpisode = "0" + str(episodeNum) + else: + tempEpisode = str(episodeNum) + + tempSeason = "" + if (str(item.get("ParentIndexNumber")) != None): + tempSeason = str(item.get("ParentIndexNumber")) + if item.get("ParentIndexNumber") < 10: + tempSeason = "0" + tempSeason + + viewType="" + if item.get("Type") == "Movie": + xbmcplugin.setContent(pluginhandle, 'movies') + viewType="_MOVIES" + elif item.get("Type") == "BoxSet": + xbmcplugin.setContent(pluginhandle, 'movies') + viewType="_BOXSETS" + elif item.get("Type") == "Trailer": + xbmcplugin.setContent(pluginhandle, 'movies') + viewType="_TRAILERS" + elif item.get("Type") == "Series": + xbmcplugin.setContent(pluginhandle, 'tvshows') + viewType="_SERIES" + elif item.get("Type") == "Season": + xbmcplugin.setContent(pluginhandle, 'seasons') + viewType="_SEASONS" + guiid = item.get("SeriesId") + elif item.get("Type") == "Episode": + prefix='' + if __settings__.getSetting('addSeasonNumber') == 'true': + prefix = "S" + str(tempSeason) + if __settings__.getSetting('addEpisodeNumber') == 'true': + prefix = prefix + "E" + #prefix = str(tempEpisode) + if __settings__.getSetting('addEpisodeNumber') == 'true': + prefix = prefix + str(tempEpisode) + if prefix != '': + tempTitle = prefix + ' - ' + tempTitle + xbmcplugin.setContent(pluginhandle, 'episodes') + viewType="_EPISODES" + guiid = item.get("SeriesId") + elif item.get("Type") == "MusicArtist": + xbmcplugin.setContent(pluginhandle, 'songs') + viewType='_MUSICARTISTS' + elif item.get("Type") == "MusicAlbum": + xbmcplugin.setContent(pluginhandle, 'songs') + viewType='_MUSICTALBUMS' + elif item.get("Type") == "Audio": + xbmcplugin.setContent(pluginhandle, 'songs') + viewType='_MUSICTRACKS' + + if(item.get("PremiereDate") != None): + premieredatelist = (item.get("PremiereDate")).split("T") + premieredate = premieredatelist[0] + else: + premieredate = "" + + # add the premiered date for Upcoming TV + if item.get("LocationType") == "Virtual": + airtime = item.get("AirTime") + tempTitle = tempTitle + ' - ' + str(premieredate) + ' - ' + str(airtime) + + #Add show name to special TV collections RAL, NextUp etc + WINDOW = xbmcgui.Window( 10000 ) + if (WINDOW.getProperty("addshowname") == "true" and item.get("SeriesName") != None): + tempTitle=item.get("SeriesName").encode('utf-8') + " - " + tempTitle + else: + tempTitle=tempTitle + + # Process MediaStreams + channels = '' + videocodec = '' + audiocodec = '' + height = '' + width = '' + aspectratio = '1:1' + aspectfloat = 1.85 + mediaStreams = item.get("MediaStreams") + if(mediaStreams != None): + for mediaStream in mediaStreams: + if(mediaStream.get("Type") == "Video"): + videocodec = mediaStream.get("Codec") + height = str(mediaStream.get("Height")) + width = str(mediaStream.get("Width")) + aspectratio = mediaStream.get("AspectRatio") + if aspectratio != None and len(aspectratio) >= 3: + try: + aspectwidth,aspectheight = aspectratio.split(':') + aspectfloat = float(aspectwidth) / float(aspectheight) + except: + aspectfloat = 1.85 + if(mediaStream.get("Type") == "Audio"): + audiocodec = mediaStream.get("Codec") + channels = mediaStream.get("Channels") + + # Process People + director='' + writer='' + cast=[] + people = item.get("People") + if(people != None): + for person in people: + if(person.get("Type") == "Director"): + director = director + person.get("Name") + ' ' + if(person.get("Type") == "Writing"): + writer = person.get("Name") + if(person.get("Type") == "Writer"): + writer = person.get("Name") + if(person.get("Type") == "Actor"): + Name = person.get("Name") + Role = person.get("Role") + if Role == None: + Role = '' + cast.append(Name) + + # Process Studios + studio = "" + studios = item.get("Studios") + if(studios != None): + for studio_string in studios: + if studio=="": #Just take the first one + temp=studio_string.get("Name") + studio=temp.encode('utf-8') + # Process Genres + genre = "" + genres = item.get("Genres") + if(genres != None and genres != []): + for genre_string in genres: + if genre == "": #Just take the first genre + genre = genre_string + elif genre_string != None: + genre = genre + " / " + genre_string + + # Process UserData + userData = item.get("UserData") + PlaybackPositionTicks = '100' + overlay = "0" + favorite = "false" + seekTime = 0 + if(userData != None): + if userData.get("Played") != True: + overlay = "7" + watched = "true" + else: + overlay = "6" + watched = "false" + if userData.get("IsFavorite") == True: + overlay = "5" + favorite = "true" + else: + favorite = "false" + if userData.get("PlaybackPositionTicks") != None: + PlaybackPositionTicks = str(userData.get("PlaybackPositionTicks")) + reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000 + seekTime = reasonableTicks / 10000 + + playCount = 0 + if(userData != None and userData.get("Played") == True): + playCount = 1 + # Populate the details list + details={'title' : tempTitle, + 'plot' : item.get("Overview"), + 'episode' : tempEpisode, + #'watched' : watched, + 'Overlay' : overlay, + 'playcount' : str(playCount), + #'aired' : episode.get('originallyAvailableAt','') , + 'TVShowTitle' : item.get("SeriesName"), + 'season' : tempSeason, + 'Video3DFormat' : item.get("Video3DFormat"), + } + + try: + tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + RunTimeTicks = str(item.get("RunTimeTicks", "0")) + except TypeError: + try: + tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60)) + RunTimeTicks = str(item.get("CumulativeRunTimeTicks")) + except TypeError: + tempDuration = "0" + RunTimeTicks = "0" + TotalSeasons = 0 if item.get("ChildCount")==None else item.get("ChildCount") + TotalEpisodes = 0 if item.get("RecursiveItemCount")==None else item.get("RecursiveItemCount") + WatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else TotalEpisodes-userData.get("UnplayedItemCount") + UnWatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount") + NumEpisodes = TotalEpisodes + # Populate the extraData list + extraData={'thumb' : downloadUtils.getArtwork(item, "Primary") , + 'fanart_image' : downloadUtils.getArtwork(item, "Backdrop") , + 'poster' : downloadUtils.getArtwork(item, "poster") , + 'tvshow.poster': downloadUtils.getArtwork(item, "tvshow.poster") , + 'banner' : downloadUtils.getArtwork(item, "Banner") , + 'clearlogo' : downloadUtils.getArtwork(item, "Logo") , + 'discart' : downloadUtils.getArtwork(item, "Disc") , + 'clearart' : downloadUtils.getArtwork(item, "Art") , + 'landscape' : downloadUtils.getArtwork(item, "Thumb") , + 'medium_landscape': downloadUtils.getArtwork(item, "Thumb3") , + 'small_poster' : downloadUtils.getArtwork(item, "Primary2") , + 'medium_poster': downloadUtils.getArtwork(item, "Primary3") , + 'small_fanartimage' : downloadUtils.getArtwork(item, "Backdrop2") , + 'medium_fanartimage' : downloadUtils.getArtwork(item, "Backdrop3") , + 'id' : id , + 'guiid' : guiid , + 'mpaa' : item.get("OfficialRating"), + 'rating' : item.get("CommunityRating"), + 'criticrating' : item.get("CriticRating"), + 'year' : item.get("ProductionYear"), + 'locationtype' : item.get("LocationType"), + 'premieredate' : premieredate, + 'studio' : studio, + 'genre' : genre, + 'playcount' : str(playCount), + 'director' : director, + 'writer' : writer, + 'channels' : channels, + 'videocodec' : videocodec, + 'aspectratio' : str(aspectfloat), + 'audiocodec' : audiocodec, + 'height' : height, + 'width' : width, + 'cast' : cast, + 'favorite' : favorite, + 'watchedurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id, + 'favoriteurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/FavoriteItems/' + id, + 'deleteurl' : 'http://' + server + '/mediabrowser/Items/' + id, + 'parenturl' : url, + 'resumetime' : str(seekTime), + 'totaltime' : tempDuration, + 'duration' : tempDuration, + 'RecursiveItemCount' : item.get("RecursiveItemCount"), + 'RecursiveUnplayedItemCount' : userData.get("UnplayedItemCount"), + 'TotalSeasons' : str(TotalSeasons), + 'TotalEpisodes': str(TotalEpisodes), + 'WatchedEpisodes': str(WatchedEpisodes), + 'UnWatchedEpisodes': str(UnWatchedEpisodes), + 'NumEpisodes' : str(NumEpisodes), + 'itemtype' : item_type} + + + + if extraData['thumb'] == '': + extraData['thumb'] = extraData['fanart_image'] + + extraData['mode'] = _MODE_GETCONTENT + + if isFolder == True: + SortByTemp = __settings__.getSetting('sortby') + if SortByTemp == '' and not (item_type == 'Series' or item_type == 'Season' or item_type == 'BoxSet' or item_type == 'MusicAlbum' or item_type == 'MusicArtist'): + SortByTemp = 'SortName' + if item_type=='Series' and __settings__.getSetting('flattenSeasons')=='true': + u = 'http://' + server + '/mediabrowser/Users/'+ userid + '/items?ParentId=' +id +'&IncludeItemTypes=Episode&Recursive=true&IsVirtualUnAired=false&IsMissing=false&Fields=' + detailsString + '&SortBy=SortName'+'&format=json' + else: + u = 'http://' + server + '/mediabrowser/Users/'+ userid + '/items?ParentId=' +id +'&IsVirtualUnAired=false&IsMissing=false&Fields=' + detailsString + '&SortBy='+SortByTemp+'&format=json' + if (item.get("RecursiveItemCount") != 0): + dirItems.append(addGUIItem(u, details, extraData)) + else: + u = server+',;'+id + dirItems.append(addGUIItem(u, details, extraData, folder=False)) + + return dirItems + +def processSearch(url, results, progress): + cast=['None'] + printDebug("== ENTER: processSearch ==") + parsed = urlparse(url) + parsedserver,parsedport=parsed.netloc.split(':') + userid = downloadUtils.getUserId() + xbmcplugin.setContent(pluginhandle, 'movies') + detailsString = "Path,Genres,Studios,CumulativeRunTimeTicks" + if(__settings__.getSetting('includeStreamInfo') == "true"): + detailsString += ",MediaStreams" + if(__settings__.getSetting('includePeople') == "true"): + detailsString += ",People" + if(__settings__.getSetting('includeOverview') == "true"): + detailsString += ",Overview" + server = getServerFromURL(url) + setWindowHeading(url) + + dirItems = [] + result = results.get("SearchHints") + if(result == None): + result = [] + + item_count = len(result) + current_item = 1; + + for item in result: + id=str(item.get("ItemId")).encode('utf-8') + type=item.get("Type").encode('utf-8') + + if(progress != None): + percentDone = (float(current_item) / float(item_count)) * 100 + progress.update(int(percentDone), __language__(30126) + str(current_item)) + current_item = current_item + 1 + + if(item.get("Name") != None): + tempTitle = item.get("Name") + tempTitle=tempTitle.encode('utf-8') + else: + tempTitle = "Missing Title" + + if type=="Series" or type=="MusicArtist" or type=="MusicAlbum" or type=="Folder": + isFolder = True + else: + isFolder = False + item_type = str(type).encode('utf-8') + + tempEpisode = "" + if (item.get("IndexNumber") != None): + episodeNum = item.get("IndexNumber") + if episodeNum < 10: + tempEpisode = "0" + str(episodeNum) + else: + tempEpisode = str(episodeNum) + + tempSeason = "" + if (str(item.get("ParentIndexNumber")) != None): + tempSeason = str(item.get("ParentIndexNumber")) + + if type == "Episode" and __settings__.getSetting('addEpisodeNumber') == 'true': + tempTitle = str(tempEpisode) + ' - ' + tempTitle + + #Add show name to special TV collections RAL, NextUp etc + WINDOW = xbmcgui.Window( 10000 ) + if type==None: + type='' + if item.get("Series")!=None: + series=item.get("Series").encode('utf-8') + tempTitle=type + ": " + series + " - " + tempTitle + else: + tempTitle=type + ": " +tempTitle + # Populate the details list + details={'title' : tempTitle, + 'episode' : tempEpisode, + 'TVShowTitle' : item.get("Series"), + 'season' : tempSeason + } + + try: + tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + RunTimeTicks = str(item.get("RunTimeTicks", "0")) + except TypeError: + try: + tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60)) + RunTimeTicks = str(item.get("CumulativeRunTimeTicks")) + except TypeError: + tempDuration = "0" + RunTimeTicks = "0" + + # Populate the extraData list + extraData={'thumb' : downloadUtils.getArtwork(item, "Primary") , + 'fanart_image' : downloadUtils.getArtwork(item, "Backdrop") , + 'poster' : downloadUtils.getArtwork(item, "poster") , + 'tvshow.poster': downloadUtils.getArtwork(item, "tvshow.poster") , + 'banner' : downloadUtils.getArtwork(item, "Banner") , + 'clearlogo' : downloadUtils.getArtwork(item, "Logo") , + 'discart' : downloadUtils.getArtwork(item, "Disc") , + 'clearart' : downloadUtils.getArtwork(item, "Art") , + 'landscape' : downloadUtils.getArtwork(item, "landscape") , + 'id' : id , + 'year' : item.get("ProductionYear"), + 'watchedurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id, + 'favoriteurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/FavoriteItems/' + id, + 'deleteurl' : 'http://' + server + '/mediabrowser/Items/' + id, + 'parenturl' : url, + 'totaltime' : tempDuration, + 'duration' : tempDuration, + 'itemtype' : item_type} + + if extraData['thumb'] == '': + extraData['thumb'] = extraData['fanart_image'] + + extraData['mode'] = _MODE_GETCONTENT + if isFolder == True: + u = 'http://' + server + '/mediabrowser/Users/'+ userid + '/items?ParentId=' +id +'&IsVirtualUnAired=false&IsMissing=false&Fields=' + detailsString + '&format=json' + dirItems.append(addGUIItem(u, details, extraData)) + elif tempDuration != '0': + u = server+',;'+id + dirItems.append(addGUIItem(u, details, extraData, folder=False)) + return dirItems + +def processChannels(url, results, progress): + global viewType + printDebug("== ENTER: processChannels ==") + parsed = urlparse(url) + parsedserver,parsedport=parsed.netloc.split(':') + userid = downloadUtils.getUserId() + xbmcplugin.setContent(pluginhandle, 'movies') + detailsString = "Path,Genres,Studios,CumulativeRunTimeTicks" + if(__settings__.getSetting('includeStreamInfo') == "true"): + detailsString += ",MediaStreams" + if(__settings__.getSetting('includePeople') == "true"): + detailsString += ",People" + if(__settings__.getSetting('includeOverview') == "true"): + detailsString += ",Overview" + server = getServerFromURL(url) + dirItems = [] + result = results.get("Items") + if(result == None): + result = [] + + item_count = len(result) + current_item = 1; + + for item in result: + id=str(item.get("Id")).encode('utf-8') + type=item.get("Type").encode('utf-8') + + if(progress != None): + percentDone = (float(current_item) / float(item_count)) * 100 + progress.update(int(percentDone), __language__(30126) + str(current_item)) + current_item = current_item + 1 + + if(item.get("Name") != None): + tempTitle = item.get("Name") + tempTitle=tempTitle.encode('utf-8') + else: + tempTitle = "Missing Title" + + if type=="ChannelFolderItem": + isFolder = True + else: + isFolder = False + item_type = str(type).encode('utf-8') + + if(item.get("ChannelId") != None): + channelId = str(item.get("ChannelId")).encode('utf-8') + # Populate the details list + details={'title' : tempTitle} + + viewType="" + if item.get("Type") == "ChannelVideoItem": + xbmcplugin.setContent(pluginhandle, 'movies') + viewType="_MOVIES" + elif item.get("Type") == "ChannelAudioItem": + xbmcplugin.setContent(pluginhandle, 'songs') + viewType='_MUSICTRACKS' + + try: + tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + RunTimeTicks = str(item.get("RunTimeTicks", "0")) + except TypeError: + try: + tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60)) + RunTimeTicks = str(item.get("CumulativeRunTimeTicks")) + except TypeError: + tempDuration = "0" + RunTimeTicks = "0" + + # Populate the extraData list + extraData={'thumb' : downloadUtils.getArtwork(item, "Primary") , + 'fanart_image' : downloadUtils.getArtwork(item, "Backdrop") , + 'poster' : downloadUtils.getArtwork(item, "poster") , + 'tvshow.poster': downloadUtils.getArtwork(item, "tvshow.poster") , + 'banner' : downloadUtils.getArtwork(item, "Banner") , + 'clearlogo' : downloadUtils.getArtwork(item, "Logo") , + 'discart' : downloadUtils.getArtwork(item, "Disc") , + 'clearart' : downloadUtils.getArtwork(item, "Art") , + 'landscape' : downloadUtils.getArtwork(item, "Thumb") , + 'id' : id , + 'year' : item.get("ProductionYear"), + 'watchedurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id, + 'favoriteurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/FavoriteItems/' + id, + 'deleteurl' : 'http://' + server + '/mediabrowser/Items/' + id, + 'parenturl' : url, + 'totaltime' : tempDuration, + 'duration' : tempDuration, + 'itemtype' : item_type} + + if extraData['thumb'] == '': + extraData['thumb'] = extraData['fanart_image'] + + extraData['mode'] = _MODE_GETCONTENT + if type=="Channel": + u = 'http://' + server + '/mediabrowser/Channels/'+ id + '/Items?userid=' +userid + '&format=json' + dirItems.append(addGUIItem(u, details, extraData)) + + elif isFolder == True: + u = 'http://' + server + '/mediabrowser/Channels/'+ channelId + '/Items?userid=' +userid + '&folderid=' + id + '&format=json' + dirItems.append(addGUIItem(u, details, extraData)) + else: + u = server+',;'+id + dirItems.append(addGUIItem(u, details, extraData, folder=False)) + return dirItems + +def processPlaylists(url, results, progress): + global viewType + printDebug("== ENTER: processPlaylists ==") + parsed = urlparse(url) + parsedserver,parsedport=parsed.netloc.split(':') + userid = downloadUtils.getUserId() + xbmcplugin.setContent(pluginhandle, 'movies') + detailsString = "" + server = getServerFromURL(url) + dirItems = [] + result = results.get("Items") + if(result == None): + result = [] + + item_count = len(result) + current_item = 1; + + for item in result: + id=str(item.get("Id")).encode('utf-8') + type=item.get("Type").encode('utf-8') + + if(progress != None): + percentDone = (float(current_item) / float(item_count)) * 100 + progress.update(int(percentDone), __language__(30126) + str(current_item)) + current_item = current_item + 1 + + if(item.get("Name") != None): + tempTitle = item.get("Name") + tempTitle=tempTitle.encode('utf-8') + else: + tempTitle = "Missing Title" + + + isFolder = False + item_type = str(type).encode('utf-8') + + + # Populate the details list + details={'title' : tempTitle} + + xbmcplugin.setContent(pluginhandle, 'movies') + viewType="_MOVIES" + + try: + tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + RunTimeTicks = str(item.get("RunTimeTicks", "0")) + except TypeError: + try: + tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60)) + RunTimeTicks = str(item.get("CumulativeRunTimeTicks")) + except TypeError: + tempDuration = "0" + RunTimeTicks = "0" + + # Populate the extraData list + extraData={'thumb' : downloadUtils.getArtwork(item, "Primary") , + 'fanart_image' : downloadUtils.getArtwork(item, "Backdrop") , + 'poster' : downloadUtils.getArtwork(item, "poster") , + 'tvshow.poster': downloadUtils.getArtwork(item, "tvshow.poster") , + 'banner' : downloadUtils.getArtwork(item, "Banner") , + 'clearlogo' : downloadUtils.getArtwork(item, "Logo") , + 'discart' : downloadUtils.getArtwork(item, "Disc") , + 'clearart' : downloadUtils.getArtwork(item, "Art") , + 'landscape' : downloadUtils.getArtwork(item, "Thumb") , + 'id' : id , + 'year' : item.get("ProductionYear"), + 'watchedurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id, + 'favoriteurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/FavoriteItems/' + id, + 'deleteurl' : 'http://' + server + '/mediabrowser/Items/' + id, + 'parenturl' : url, + 'totaltime' : tempDuration, + 'duration' : tempDuration, + 'itemtype' : item_type} + + if extraData['thumb'] == '': + extraData['thumb'] = extraData['fanart_image'] + + extraData['mode'] = _MODE_GETCONTENT + + u = server+',;'+id+',;'+'PLAYLIST' + dirItems.append(addGUIItem(u, details, extraData, folder=False)) + return dirItems + +def processGenres(url, results, progress, content): + global viewType + printDebug("== ENTER: processGenres ==") + parsed = urlparse(url) + parsedserver,parsedport=parsed.netloc.split(':') + userid = downloadUtils.getUserId() + xbmcplugin.setContent(pluginhandle, 'movies') + detailsString = "Path,Genres,Studios,CumulativeRunTimeTicks" + if(__settings__.getSetting('includeStreamInfo') == "true"): + detailsString += ",MediaStreams" + if(__settings__.getSetting('includePeople') == "true"): + detailsString += ",People" + if(__settings__.getSetting('includeOverview') == "true"): + detailsString += ",Overview" + server = getServerFromURL(url) + dirItems = [] + result = results.get("Items") + if(result == None): + result = [] + + item_count = len(result) + current_item = 1; + + for item in result: + id=str(item.get("Id")).encode('utf-8') + type=item.get("Type").encode('utf-8') + item_type = str(type).encode('utf-8') + if(progress != None): + percentDone = (float(current_item) / float(item_count)) * 100 + progress.update(int(percentDone), __language__(30126) + str(current_item)) + current_item = current_item + 1 + + if(item.get("Name") != None): + tempTitle = item.get("Name") + tempTitle=tempTitle.encode('utf-8') + else: + tempTitle = "Missing Title" + + + isFolder = True + + + # Populate the details list + details={'title' : tempTitle} + + viewType="_MOVIES" + + try: + tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + RunTimeTicks = str(item.get("RunTimeTicks", "0")) + except TypeError: + try: + tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60)) + RunTimeTicks = str(item.get("CumulativeRunTimeTicks")) + except TypeError: + tempDuration = "0" + RunTimeTicks = "0" + + # Populate the extraData list + extraData={'thumb' : downloadUtils.getArtwork(item, "Primary") , + 'fanart_image' : downloadUtils.getArtwork(item, "Backdrop") , + 'poster' : downloadUtils.getArtwork(item, "poster") , + 'tvshow.poster': downloadUtils.getArtwork(item, "tvshow.poster") , + 'banner' : downloadUtils.getArtwork(item, "Banner") , + 'clearlogo' : downloadUtils.getArtwork(item, "Logo") , + 'discart' : downloadUtils.getArtwork(item, "Disc") , + 'clearart' : downloadUtils.getArtwork(item, "Art") , + 'landscape' : downloadUtils.getArtwork(item, "Thumb") , + 'id' : id , + 'year' : item.get("ProductionYear"), + 'watchedurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id, + 'favoriteurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/FavoriteItems/' + id, + 'deleteurl' : 'http://' + server + '/mediabrowser/Items/' + id, + 'parenturl' : url, + 'totaltime' : tempDuration, + 'duration' : tempDuration, + 'itemtype' : item_type} + + if extraData['thumb'] == '': + extraData['thumb'] = extraData['fanart_image'] + + extraData['mode'] = _MODE_GETCONTENT + + u = 'http://' + server + '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=' + detailsString + '&Recursive=true&SortOrder=Ascending&IncludeItemTypes=' + content + '&Genres=' + item.get("Name") + '&format=json' + dirItems.append(addGUIItem(u, details, extraData)) + + return dirItems + +def processStudios(url, results, progress, content): + global viewType + printDebug("== ENTER: processStudios ==") + parsed = urlparse(url) + parsedserver,parsedport=parsed.netloc.split(':') + userid = downloadUtils.getUserId() + xbmcplugin.setContent(pluginhandle, 'movies') + detailsString = "Path,Genres,Studios,CumulativeRunTimeTicks" + if(__settings__.getSetting('includeStreamInfo') == "true"): + detailsString += ",MediaStreams" + if(__settings__.getSetting('includePeople') == "true"): + detailsString += ",People" + if(__settings__.getSetting('includeOverview') == "true"): + detailsString += ",Overview" + server = getServerFromURL(url) + dirItems = [] + result = results.get("Items") + if(result == None): + result = [] + + item_count = len(result) + current_item = 1; + + for item in result: + id=str(item.get("Id")).encode('utf-8') + type=item.get("Type").encode('utf-8') + item_type = str(type).encode('utf-8') + if(progress != None): + percentDone = (float(current_item) / float(item_count)) * 100 + progress.update(int(percentDone), __language__(30126) + str(current_item)) + current_item = current_item + 1 + + if(item.get("Name") != None): + tempTitle = item.get("Name") + tempTitle=tempTitle.encode('utf-8') + else: + tempTitle = "Missing Title" + + + isFolder = True + + + # Populate the details list + details={'title' : tempTitle} + + viewType="_MOVIES" + + try: + tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + RunTimeTicks = str(item.get("RunTimeTicks", "0")) + except TypeError: + try: + tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60)) + RunTimeTicks = str(item.get("CumulativeRunTimeTicks")) + except TypeError: + tempDuration = "0" + RunTimeTicks = "0" + + # Populate the extraData list + extraData={'thumb' : downloadUtils.getArtwork(item, "Primary") , + 'fanart_image' : downloadUtils.getArtwork(item, "Backdrop") , + 'poster' : downloadUtils.getArtwork(item, "poster") , + 'tvshow.poster': downloadUtils.getArtwork(item, "tvshow.poster") , + 'banner' : downloadUtils.getArtwork(item, "Banner") , + 'clearlogo' : downloadUtils.getArtwork(item, "Logo") , + 'discart' : downloadUtils.getArtwork(item, "Disc") , + 'clearart' : downloadUtils.getArtwork(item, "Art") , + 'landscape' : downloadUtils.getArtwork(item, "Thumb") , + 'id' : id , + 'year' : item.get("ProductionYear"), + 'watchedurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id, + 'favoriteurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/FavoriteItems/' + id, + 'deleteurl' : 'http://' + server + '/mediabrowser/Items/' + id, + 'parenturl' : url, + 'totaltime' : tempDuration, + 'duration' : tempDuration, + 'itemtype' : item_type} + + if extraData['thumb'] == '': + extraData['thumb'] = extraData['fanart_image'] + + extraData['mode'] = _MODE_GETCONTENT + xbmc.log("XBMB3C - process studios nocode: " + tempTitle) + tempTitle = tempTitle.replace(' ', '+') + xbmc.log("XBMB3C - process studios nocode spaces replaced: " + tempTitle) + tempTitle2 = unicode(tempTitle,'utf-8') + u = 'http://' + server + '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=' + detailsString + '&Recursive=true&SortOrder=Ascending&IncludeItemTypes=' + content + '&Studios=' + tempTitle2.encode('ascii','ignore') + '&format=json' + xbmc.log("XBMB3C - process studios: " + u) + dirItems.append(addGUIItem(u, details, extraData)) + + return dirItems + +def processPeople(url, results, progress, content): + global viewType + printDebug("== ENTER: processPeople ==") + parsed = urlparse(url) + parsedserver,parsedport=parsed.netloc.split(':') + userid = downloadUtils.getUserId() + xbmcplugin.setContent(pluginhandle, 'movies') + detailsString = "Path,Genres,Studios,CumulativeRunTimeTicks" + if(__settings__.getSetting('includeStreamInfo') == "true"): + detailsString += ",MediaStreams" + if(__settings__.getSetting('includePeople') == "true"): + detailsString += ",People" + if(__settings__.getSetting('includeOverview') == "true"): + detailsString += ",Overview" + server = getServerFromURL(url) + dirItems = [] + result = results.get("Items") + if(result == None): + result = [] + + item_count = len(result) + current_item = 1; + + for item in result: + id=str(item.get("Id")).encode('utf-8') + type=item.get("Type").encode('utf-8') + item_type = str(type).encode('utf-8') + if(progress != None): + percentDone = (float(current_item) / float(item_count)) * 100 + progress.update(int(percentDone), __language__(30126) + str(current_item)) + current_item = current_item + 1 + + if(item.get("Name") != None): + tempTitle = item.get("Name") + tempTitle=tempTitle.encode('utf-8') + else: + tempTitle = "Missing Title" + + + isFolder = True + + + # Populate the details list + details={'title' : tempTitle} + + viewType="_MOVIES" + + try: + tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + RunTimeTicks = str(item.get("RunTimeTicks", "0")) + except TypeError: + try: + tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60)) + RunTimeTicks = str(item.get("CumulativeRunTimeTicks")) + except TypeError: + tempDuration = "0" + RunTimeTicks = "0" + + # Populate the extraData list + extraData={'thumb' : downloadUtils.getArtwork(item, "Primary") , + 'fanart_image' : downloadUtils.getArtwork(item, "Backdrop") , + 'poster' : downloadUtils.getArtwork(item, "poster") , + 'tvshow.poster': downloadUtils.getArtwork(item, "tvshow.poster") , + 'banner' : downloadUtils.getArtwork(item, "Banner") , + 'clearlogo' : downloadUtils.getArtwork(item, "Logo") , + 'discart' : downloadUtils.getArtwork(item, "Disc") , + 'clearart' : downloadUtils.getArtwork(item, "Art") , + 'landscape' : downloadUtils.getArtwork(item, "landscape") , + 'id' : id , + 'year' : item.get("ProductionYear"), + 'watchedurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id, + 'favoriteurl' : 'http://' + server + '/mediabrowser/Users/'+ userid + '/FavoriteItems/' + id, + 'deleteurl' : 'http://' + server + '/mediabrowser/Items/' + id, + 'parenturl' : url, + 'totaltime' : tempDuration, + 'duration' : tempDuration, + 'itemtype' : item_type} + + if extraData['thumb'] == '': + extraData['thumb'] = extraData['fanart_image'] + + extraData['mode'] = _MODE_GETCONTENT + xbmc.log("XBMB3C - process people nocode: " + tempTitle) + tempTitle = tempTitle.replace(' ', '+') + xbmc.log("XBMB3C - process people nocode spaces replaced: " + tempTitle) + tempTitle2 = unicode(tempTitle,'utf-8') + u = 'http://' + server + '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=' + detailsString + '&Recursive=true&SortOrder=Ascending&IncludeItemTypes=' + content + '&Person=' + tempTitle2.encode('ascii','ignore') + '&format=json' + xbmc.log("XBMB3C - process people: " + u) + dirItems.append(addGUIItem(u, details, extraData)) + + return dirItems + +def getServerFromURL( url ): + ''' + Simply split the URL up and get the server portion, sans port + @ input: url, woth or without protocol + @ return: the URL server + ''' + if url[0:4] == "http": + return url.split('/')[2] + else: + return url.split('/')[0] + +def getLinkURL( url, pathData, server ): + ''' + Investigate the passed URL and determine what is required to + turn it into a usable URL + @ input: url, XML data and PM server address + @ return: Usable http URL + ''' + printDebug("== ENTER: getLinkURL ==") + path=pathData.get('key','') + printDebug("Path is " + path) + + if path == '': + printDebug("Empty Path") + return + + #If key starts with http, then return it + if path[0:4] == "http": + printDebug("Detected http link") + return path + + #If key starts with a / then prefix with server address + elif path[0] == '/': + printDebug("Detected base path link") + return 'http://%s%s' % ( server, path ) + + elif path[0:5] == "rtmp:": + printDebug("Detected link") + return path + + #Any thing else is assumed to be a relative path and is built on existing url + else: + printDebug("Detected relative link") + return "%s/%s" % ( url, path ) + + return url + +def setArt (list,name,path): + if name=='thumb' or name=='fanart_image' or name=='small_poster' or name == "medium_landscape" or name=='medium_poster' or name=='small_fanartimage' or name=='medium_fanartimage': + list.setProperty(name, path) + elif xbmcVersionNum >= 13: + list.setArt({name:path}) + return list + +def getXbmcVersion(): + version = 0.0 + jsonData = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["version", "name"]}, "id": 1 }') + + result = json.loads(jsonData) + + try: + result = result.get("result") + versionData = result.get("version") + version = float(str(versionData.get("major")) + "." + str(versionData.get("minor"))) + printDebug("Version : " + str(version) + " - " + str(versionData), level=0) + except: + version = 0.0 + printDebug("Version Error : RAW Version Data : " + str(result), level=0) + + return version + +def setWindowHeading(url) : + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty("addshowname", "false") + WINDOW.setProperty("currenturl", url) + WINDOW.setProperty("currentpluginhandle", str(pluginhandle)) + if 'ParentId' in url: + dirUrl = url.replace('items?ParentId=','Items/') + splitUrl = dirUrl.split('&') + dirUrl = splitUrl[0] + '?format=json' + jsonData = downloadUtils.downloadUrl(dirUrl) + result = json.loads(jsonData) + for name in result: + title = name + WINDOW.setProperty("heading", title) + elif 'IncludeItemTypes=Episode' in url: + WINDOW.setProperty("addshowname", "true") + +def getCastList(pluginName, handle, params): + + printDebug ("XBMB3C Returning Cast List") + + port = __settings__.getSetting('port') + host = __settings__.getSetting('ipaddress') + server = host + ":" + port + userid = downloadUtils.getUserId() + seekTime = 0 + resume = 0 + + # get the cast list for an item + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + params.get("id") + "?format=json", suppress=False, popup=1 ) + printDebug("CastList(Items) jsonData: " + jsonData, 2) + result = json.loads(jsonData) + + people = result.get("People") + + if(people == None): + return + + listItems = [] + + for person in people: + + displayName = person.get("Name") + if(person.get("Role") != None): + displayName = displayName + " (" + person.get("Role") + ")" + + tag = person.get("PrimaryImageTag") + id = person.get("Id") + + baseName = person.get("Name") + #urllib.quote(baseName) + baseName = baseName.replace(" ", "+") + baseName = baseName.replace("&", "_") + baseName = baseName.replace("?", "_") + baseName = baseName.replace("=", "_") + + if(tag != None): + thumbPath = downloadUtils.imageUrl(id, "Primary", 0, 400, 400) + item = xbmcgui.ListItem(label=displayName, iconImage=thumbPath, thumbnailImage=thumbPath) + else: + item = xbmcgui.ListItem(label=displayName) + + actionUrl = "plugin://plugin.video.xbmb3c?mode=" + str(_MODE_PERSON_DETAILS) +"&name=" + baseName + + item.setProperty('IsPlayable', 'false') + item.setProperty('IsFolder', 'false') + + commands = [] + detailsString = getDetailsString() + url = "http://" + host + ":" + port + "/mediabrowser/Users/" + userid + "/Items/?Recursive=True&Person=PERSON_NAME&Fields=" + detailsString + "&format=json" + url = urllib.quote(url) + url = url.replace("PERSON_NAME", baseName) + pluginCastLink = "XBMC.Container.Update(plugin://plugin.video.xbmb3c?mode=" + str(_MODE_GETCONTENT) + "&url=" + url + ")" + commands.append(( "Show Other Library Items", pluginCastLink)) + item.addContextMenuItems( commands, g_contextReplace ) + + itemTupple = (actionUrl, item, False) + listItems.append(itemTupple) + + + #listItems.sort() + xbmcplugin.addDirectoryItems(handle, listItems) + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) + +def showItemInfo(pluginName, handle, params): + printDebug("showItemInfo Called" + str(params)) + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) + + infoPage = ItemInfo("ItemInfo.xml", __cwd__, "default", "720p") + + infoPage.setId(params.get("id")) + infoPage.doModal() + + del infoPage + +def showSearch(pluginName, handle, params): + printDebug("showSearch Called" + str(params)) + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) + + searchDialog = SearchDialog("SearchDialog.xml", __cwd__, "default", "720p") + + searchDialog.doModal() + + del searchDialog + +def showPersonInfo(pluginName, handle, params): + printDebug("showPersonInfo Called" + str(params)) + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) + + infoPage = PersonInfo("PersonInfo.xml", __cwd__, "default", "720p") + + infoPage.setPersonName(params.get("name")) + infoPage.doModal() + + if(infoPage.showMovies == True): + xbmc.log("RUNNING_PLUGIN: " + infoPage.pluginCastLink) + xbmc.executebuiltin(infoPage.pluginCastLink) + + del infoPage + +def getWigetContent(pluginName, handle, params): + printDebug("getWigetContent Called" + str(params)) + + port = __settings__.getSetting('port') + host = __settings__.getSetting('ipaddress') + server = host + ":" + port + + collectionType = params.get("CollectionType") + type = params.get("type") + parentId = params.get("ParentId") + + if(type == None): + printDebug("getWigetContent No Type") + return + + userid = downloadUtils.getUserId() + + if(type == "recent"): + itemsUrl = "http://" + server + "/mediabrowser/Users/" + userid + "/items?ParentId=" + parentId + "&Limit=10&SortBy=DateCreated&Fields=Path,Overview&SortOrder=Descending&Filters=IsNotFolder&IncludeItemTypes=Movie,Episode,Trailer,Musicvideo,Video&CollapseBoxSetItems=false&IsVirtualUnaired=false&Recursive=true&IsMissing=False&format=json" + elif(type == "active"): + itemsUrl = "http://" + server + "/mediabrowser/Users/" + userid + "/items?ParentId=" + parentId + "&Limit=10&SortBy=DatePlayed&Fields=Path,Overview&SortOrder=Descending&Filters=IsResumable,IsNotFolder&IncludeItemTypes=Movie,Episode,Trailer,Musicvideo,Video&CollapseBoxSetItems=false&IsVirtualUnaired=false&Recursive=true&IsMissing=False&format=json" + + printDebug("WIDGET_DATE_URL: " + itemsUrl, 2) + + # get the recent items + jsonData = downloadUtils.downloadUrl(itemsUrl, suppress=False, popup=1 ) + printDebug("Recent(Items) jsonData: " + jsonData, 2) + result = json.loads(jsonData) + + result = result.get("Items") + if(result == None): + result = [] + + itemCount = 1 + listItems = [] + for item in result: + item_id = item.get("Id") + + image_id = item_id + if item.get("Type") == "Episode": + image_id = item.get("SeriesId") + + #image = downloadUtils.getArtwork(item, "Primary") + image = downloadUtils.imageUrl(image_id, "Primary", 0, 400, 400) + fanart = downloadUtils.getArtwork(item, "Backdrop") + + Duration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + + name = item.get("Name") + printDebug("WIDGET_DATE_NAME: " + name, 2) + + seriesName = '' + if(item.get("SeriesName") != None): + seriesName = item.get("SeriesName").encode('utf-8') + + eppNumber = "X" + tempEpisodeNumber = "00" + if(item.get("IndexNumber") != None): + eppNumber = item.get("IndexNumber") + if eppNumber < 10: + tempEpisodeNumber = "0" + str(eppNumber) + else: + tempEpisodeNumber = str(eppNumber) + + seasonNumber = item.get("ParentIndexNumber") + if seasonNumber < 10: + tempSeasonNumber = "0" + str(seasonNumber) + else: + tempSeasonNumber = str(seasonNumber) + + name = tempSeasonNumber + "x" + tempEpisodeNumber + "-" + name + + list_item = xbmcgui.ListItem(label=name, iconImage=image, thumbnailImage=image) + list_item.setInfo( type="Video", infoLabels={ "year":item.get("ProductionYear"), "duration":str(Duration), "plot":item.get("Overview"), "tvshowtitle":str(seriesName), "premiered":item.get("PremiereDate"), "rating":item.get("CommunityRating") } ) + list_item.setProperty('fanart_image',fanart) + + # add count + list_item.setProperty("item_index", str(itemCount)) + itemCount = itemCount + 1 + + # add progress percent + + userData = item.get("UserData") + PlaybackPositionTicks = '100' + overlay = "0" + favorite = "false" + seekTime = 0 + if(userData != None): + playBackTicks = float(userData.get("PlaybackPositionTicks")) + if(playBackTicks != None and playBackTicks > 0): + runTimeTicks = float(item.get("RunTimeTicks", "0")) + if(runTimeTicks > 0): + percentage = int((playBackTicks / runTimeTicks) * 100.0) + cappedPercentage = percentage - (percentage % 10) + if(cappedPercentage == 0): + cappedPercentage = 10 + if(cappedPercentage == 100): + cappedPercentage = 90 + list_item.setProperty("complete_percentage", str(cappedPercentage)) + + selectAction = __settings__.getSetting('selectAction') + if(selectAction == "1"): + playUrl = "plugin://plugin.video.xbmb3c/?id=" + item_id + '&mode=' + str(_MODE_ITEM_DETAILS) + else: + url = server + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + + itemTupple = (playUrl, list_item, False) + listItems.append(itemTupple) + + xbmcplugin.addDirectoryItems(handle, listItems) + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) + +def showParentContent(pluginName, handle, params): + printDebug("showParentContent Called" + str(params), 2) + + port = __settings__.getSetting('port') + host = __settings__.getSetting('ipaddress') + server = host + ":" + port + + parentId = params.get("ParentId") + name = params.get("Name") + detailsString = getDetailsString() + userid = downloadUtils.getUserId() + + contentUrl = ( + "http://" + server + + "/mediabrowser/Users/" + userid + "/items?ParentId=" + parentId + + "&IsVirtualUnaired=false" + + "&IsMissing=False" + + "&Fields=" + detailsString + + "&SortOrder=" + __settings__.getSetting('sortorderfor' + urllib.quote(name)) + + "&SortBy=" + __settings__.getSetting('sortbyfor' + urllib.quote(name)) + + "&Genres=&format=json") + + printDebug("showParentContent Content Url : " + str(contentUrl), 2) + + getContent(contentUrl) + +def showViewList(url, pluginhandle): + viewCats=['Movies', 'BoxSets', 'Trailers', 'Series', 'Seasons', 'Episodes', 'Music Artists', 'Music Albums', 'Music Videos', 'Music Tracks'] + viewTypes=['_MOVIES', '_BOXSETS', '_TRAILERS', '_SERIES', '_SEASONS', '_EPISODES', '_MUSICARTISTS', '_MUSICALBUMS', '_MUSICVIDEOS', '_MUSICTRACKS'] + if "SETVIEWS" in url: + for viewCat in viewCats: + xbmcplugin.addDirectoryItem(pluginhandle, 'plugin://plugin.video.xbmb3c/?url=_SHOWVIEWS' + viewTypes[viewCats.index(viewCat)] + '&mode=' + str(_MODE_SETVIEWS), xbmcgui.ListItem(viewCat, ''), isFolder=True) + elif "_SETVIEW_" in url: + category=url.split('_')[2] + viewNum=url.split('_')[3] + __settings__.setSetting(xbmc.getSkinDir()+ '_VIEW_' +category,viewNum) + xbmc.executebuiltin("Container.Refresh") + else: + + skin_view_file = os.path.join(xbmc.translatePath('special://skin'), "views.xml") + try: + tree = etree.parse(skin_view_file) + except: + xbmcgui.Dialog().ok(__language__(30135), __language__(30150)) + sys.exit() + root = tree.getroot() + xbmcplugin.addDirectoryItem(pluginhandle, 'plugin://plugin.video.xbmb3c?url=_SETVIEW_'+ url.split('_')[2] + '_' + '' + '&mode=' + str(_MODE_SETVIEWS), xbmcgui.ListItem('Clear Settings', 'test')) + for view in root.iter('view'): + if __settings__.getSetting(xbmc.getSkinDir()+ '_VIEW_'+ url.split('_')[2]) == view.attrib['value']: + name=view.attrib['id'] + " (Active)" + else: + name=view.attrib['id'] + xbmcplugin.addDirectoryItem(pluginhandle, 'plugin://plugin.video.xbmb3c?url=_SETVIEW_'+ url.split('_')[2] + '_' + view.attrib['value'] + '&mode=' + str(_MODE_SETVIEWS), xbmcgui.ListItem(name, 'test')) + xbmcplugin.endOfDirectory(pluginhandle, cacheToDisc=False) + +def checkService(): + + timeStamp = xbmcgui.Window(10000).getProperty("XBMB3C_Service_Timestamp") + loops = 0 + while(timeStamp == ""): + timeStamp = xbmcgui.Window(10000).getProperty("XBMB3C_Service_Timestamp") + loops = loops + 1 + if(loops == 40): + printDebug("XBMB3C Service Not Running, no time stamp, exiting", 0) + xbmcgui.Dialog().ok(__language__(30135), __language__(30136), __language__(30137)) + sys.exit() + xbmc.sleep(200) + + printDebug ("XBMB3C Service Timestamp: " + timeStamp) + printDebug ("XBMB3C Current Timestamp: " + str(int(time.time()))) + + if((int(timeStamp) + 240) < int(time.time())): + printDebug("XBMB3C Service Not Running, time stamp to old, exiting", 0) + xbmcgui.Dialog().ok(__language__(30135), __language__(30136), __language__(30137)) + sys.exit() + +def checkServer(): + printDebug ("XBMB3C checkServer Called") + + port = __settings__.getSetting('port') + host = __settings__.getSetting('ipaddress') + + if(len(host) != 0 and host != ""): + printDebug ("XBMB3C server already set") + return + + serverInfo = getServerDetails() + + if(serverInfo == None): + printDebug ("XBMB3C getServerDetails failed") + return + + index = serverInfo.find(":") + + if(index <= 0): + printDebug ("XBMB3C getServerDetails data not correct : " + serverInfo) + return + + server_address = serverInfo[:index] + server_port = serverInfo[index+1:] + printDebug ("XBMB3C detected server info " + server_address + " : " + server_port) + + xbmcgui.Dialog().ok(__language__(30167), __language__(30168), __language__(30169) + server_address, __language__(30030) + server_port) + + # get a list of users + printDebug ("Getting user list") + jsonData = None + try: + jsonData = downloadUtils.downloadUrl(server_address + ":" + server_port + "/mediabrowser/Users?format=json") + except Exception, msg: + error = "Get User unable to connect to " + server_address + ":" + server_port + " : " + str(msg) + xbmc.log (error) + return "" + + if(jsonData == False): + return + + printDebug("jsonData : " + str(jsonData), level=2) + result = json.loads(jsonData) + + names = [] + userList = [] + for user in result: + config = user.get("Configuration") + if(config != None): + if(config.get("IsHidden") == False): + name = user.get("Name") + userList.append(name) + if(user.get("HasPassword") == True): + name = name + " (Secure)" + names.append(name) + + printDebug ("User List : " + str(names)) + printDebug ("User List : " + str(userList)) + return_value = xbmcgui.Dialog().select(__language__(30200), names) + + if(return_value > -1): + selected_user = userList[return_value] + printDebug("Setting Selected User : " + selected_user) + if __settings__.getSetting("port") != server_port: + __settings__.setSetting("port", server_port) + if __settings__.getSetting("ipaddress") != server_address: + __settings__.setSetting("ipaddress", server_address) + if __settings__.getSetting("username") != selected_user: + __settings__.setSetting("username", selected_user) + +########################################################################### +##Start of Main +########################################################################### +if(logLevel == 2): + xbmcgui.Dialog().ok(__language__(30132), __language__(30133), __language__(30134)) + +printDebug( "XBMB3C -> Script argument date " + str(sys.argv)) +xbmcVersionNum = getXbmcVersion() +try: + params=get_params(sys.argv[2]) +except: + params={} +printDebug( "XBMB3C -> Script params is " + str(params)) + #Check to see if XBMC is playing - we don't want to do anything if so +#if xbmc.Player().isPlaying(): +# printDebug ('Already Playing! Exiting...') +# sys.exit() +#Now try and assign some data to them +param_url=params.get('url',None) + +if param_url and ( param_url.startswith('http') or param_url.startswith('file') ): + param_url = urllib.unquote(param_url) + +param_name = urllib.unquote_plus(params.get('name',"")) +mode = int(params.get('mode',-1)) +param_transcodeOverride = int(params.get('transcode',0)) +param_identifier = params.get('identifier',None) +param_indirect = params.get('indirect',None) +force = params.get('force') +WINDOW = xbmcgui.Window( 10000 ) +WINDOW.setProperty("addshowname","false") + +if str(sys.argv[1]) == "skin": + skin() +elif sys.argv[1] == "check_server": + checkServer() +elif sys.argv[1] == "update": + url=sys.argv[2] + libraryRefresh(url) +elif sys.argv[1] == "markWatched": + url=sys.argv[2] + markWatched(url) +elif sys.argv[1] == "markUnwatched": + url=sys.argv[2] + markUnwatched(url) +elif sys.argv[1] == "markFavorite": + url=sys.argv[2] + markFavorite(url) +elif sys.argv[1] == "unmarkFavorite": + url=sys.argv[2] + unmarkFavorite(url) +elif sys.argv[1] == "setting": + __settings__.openSettings() + WINDOW = xbmcgui.getCurrentWindowId() + if WINDOW == 10000: + printDebug("Currently in home - refreshing to allow new settings to be taken") + xbmc.executebuiltin("XBMC.ActivateWindow(Home)") +elif sys.argv[1] == "delete": + url=sys.argv[2] + delete(url) +elif sys.argv[1] == "refresh": + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") +elif sys.argv[1] == "sortby": + sortby() +elif sys.argv[1] == "sortorder": + sortorder() +elif sys.argv[1] == "genrefilter": + genrefilter() +elif sys.argv[1] == "playall": + startId=sys.argv[2] + playall(startId) +elif mode == _MODE_CAST_LIST: + getCastList(sys.argv[0], int(sys.argv[1]), params) +elif mode == _MODE_PERSON_DETAILS: + showPersonInfo(sys.argv[0], int(sys.argv[1]), params) +elif mode == _MODE_WIDGET_CONTENT: + getWigetContent(sys.argv[0], int(sys.argv[1]), params) +elif mode == _MODE_ITEM_DETAILS: + showItemInfo(sys.argv[0], int(sys.argv[1]), params) +elif mode == _MODE_SHOW_SEARCH: + showSearch(sys.argv[0], int(sys.argv[1]), params) +elif mode == _MODE_SHOW_PARENT_CONTENT: + checkService() + checkServer() + pluginhandle = int(sys.argv[1]) + showParentContent(sys.argv[0], int(sys.argv[1]), params) +else: + + checkService() + checkServer() + + pluginhandle = int(sys.argv[1]) + + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.clearProperty("heading") + #mode=_MODE_BASICPLAY + + printDebug("XBMB3C -> Mode: "+str(mode)) + printDebug("XBMB3C -> URL: "+str(param_url)) + printDebug("XBMB3C -> Name: "+str(param_name)) + printDebug("XBMB3C -> identifier: " + str(param_identifier)) + + #Run a function based on the mode variable that was passed in the URL + if ( mode == None or mode == _MODE_SHOW_SECTIONS or param_url == None or len(param_url) < 1 ): + displaySections() + + elif mode == _MODE_GETCONTENT: + if __settings__.getSetting('profile') == "true": + + xbmcgui.Dialog().ok(__language__(30201), __language__(30202), __language__(30203)) + + pr = cProfile.Profile() + pr.enable() + getContent(param_url) + pr.disable() + ps = pstats.Stats(pr) + + fileTimeStamp = time.strftime("%Y-%m-%d %H-%M-%S") + tabFileName = __addondir__ + "profile_(" + fileTimeStamp + ").tab" + f = open(tabFileName, 'wb') + f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n") + for (key, value) in ps.stats.items(): + (filename, count, func_name) = key + (ccalls, ncalls, total_time, cumulative_time, callers) = value + try: + f.write(str(ncalls) + "\t" + "{:10.4f}".format(total_time) + "\t" + "{:10.4f}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n") + except ValueError: + f.write(str(ncalls) + "\t" + "{0}".format(total_time) + "\t" + "{0}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n") + f.close() + + else: + getContent(param_url) + + elif mode == _MODE_BASICPLAY: + PLAY(param_url, pluginhandle) + elif mode == _MODE_PLAYLISTPLAY: + PLAYPlaylist(param_url, pluginhandle) + elif mode == _MODE_SEARCH: + searchString=urllib.quote(xbmcgui.Dialog().input(__language__(30138))) + printDebug("Search String : " + searchString) + if searchString == "": + sys.exit() + param_url=param_url.replace("Search/Hints?","Search/Hints?SearchTerm="+searchString + "&UserId=") + param_url=param_url + "&Fields=" + getDetailsString() + "&format=json" + getContent(param_url) + elif mode == _MODE_SETVIEWS: + showViewList(param_url, pluginhandle) + +WINDOW = xbmcgui.Window( 10000 ) +WINDOW.clearProperty("MB3.Background.Item.FanArt") +xbmc.log ("===== XBMB3C STOP =====") + +#clear done and exit. +sys.modules.clear() diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..e10aa89 Binary files /dev/null and b/icon.png differ diff --git a/resources/__init__.py b/resources/__init__.py new file mode 100644 index 0000000..b93054b --- /dev/null +++ b/resources/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml new file mode 100644 index 0000000..75723dc --- /dev/null +++ b/resources/language/English/strings.xml @@ -0,0 +1,194 @@ + + + Primary Server Address + Auto enter single folder items: + Play from HTTP instead of SMB: + Log Level: + Username: + Password: + Samba Username: + Samba Password: + Transcode: + Enable performance profiling + + MediaBrowser + Network + Device Name + + Advanced + Username: + Password: + Use SIMPLEJSON instead of JSON + + Port Number: + Number of recent Movies to show: + Number of recent TV episodes to show: + Number of recent Music Albums to show: + Mark watched at start of playback: + Set Season poster for episodes + + Genre Filter ... + Play All from Here + Refresh + Delete + Add Movie to CouchPotato + + Incorrect Username/Password + Username not found + + Deleting + Waiting for server to delete + + Server Default + Title + Year + Premiere Date + Date Created + Critic Rating + Community Rating + Play Count + Budget + + Sort By + + None + Action + Adventure + Animation + Crime + Comedy + Documentary + Drama + Fantasy + Foreign + History + Horror + Music + Musical + Mystery + Romance + Science Fiction + Short + Suspense + Thriller + Western + + Genre Filter + Confirm file delete? + Delete this item? This action will delete media and associated data files. + + Mark Watched + Mark Unwatched + Add to Favorites + Remove from Favorites + Sort By ... + Sort Order Descending + Sort Order Ascending + Show People + + Interface + Include Stream Info + Include People + Include Overview + On Resume Jump Back Seconds + Mark Played When Stopping Above % + Add Item and Played Counts + - Background Art Refresh Rate (seconds) + Add Resume Percent + Add Episode Number + Show Load Progress + Loading Content + Retrieving Data + Parsing Jason Data + Downloading Jason Data + Done + Processing Item : + Offer delete for watched episodes + Play Error + This item is not playable + Local path detected + Your MB3 Server contains local paths. Please change server paths to UNC or change XBMB3C setting 'Play from Stream' to true. Path: + Warning + Debug logging enabled. + This will affect performance. + Error + XBMB3C service is not running + Please restart XBMC + Search + + Enable Theme Music (Requires Restart) + - Loop Theme Music + Enable Background Image (Requires Restart) + Services + Enable Info Loader (Requires Restart) + Enable Menu Loader (Requires Restart) + Enable WebSocket Remote (Requires Restart) + Enable In Progress Loader (Requires Restart) + Enable Recent Info Loader (Requires Restart) + Enable Random Loader (Requires Restart) + Enable Next Up Loader (Requires Restart) + + Skin does not support setting views + Select item action (Requires Restart) + + Show Indicators + - Show Watched Indicator + - Show Unplayed Count Indicator + - Show Played Percentage Indicator + Sort NextUp by Show Title + Disable Enhanced Images (eg CoverArt) + Metadata + Artwork + Video Quality + + Enable Suggested Loader (Requires Restart) + Add Season Number + Flatten Seasons + Direct Play - HTTP + Direct Play + Transcoding + Server Detection Succeeded + Found server + Address : + + + All Movies + All TV + All Music + Channels + Recently Added Movies + Recently Added Episodes + Recently Added Albums + In Progress Movies + In Progress Episodes + Next Episodes + Favorite Movies + Favorite Shows + Favorite Episodes + Frequent Played Albums + Upcoming TV + BoxSets + Trailers + Music Videos + Photos + Unwatched Movies + Movie Genres + Movie Studios + Movie Actors + Unwatched Episodes + TV Genres + TV Networks + TV Actors + Playlists + Search + Set Views + + Select User + Profiling enabled. + Please remember to turn off when finished testing. + Error in ArtworkRotationThread + Unable to connect to host + Error in LoadMenuOptionsThread + + + \ No newline at end of file diff --git a/resources/lib/ArtworkLoader.py b/resources/lib/ArtworkLoader.py new file mode 100644 index 0000000..cf89029 --- /dev/null +++ b/resources/lib/ArtworkLoader.py @@ -0,0 +1,809 @@ +################################################################################################# +# Start of BackgroundRotationThread +# Sets a backgound property to a fan art link +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +import urllib +import urllib2 +import random +import time +from DownloadUtils import DownloadUtils + +_MODE_BASICPLAY=12 + +#define our global download utils +downloadUtils = DownloadUtils() + +class ArtworkRotationThread(threading.Thread): + + movie_art_links = [] + tv_art_links = [] + music_art_links = [] + global_art_links = [] + item_art_links = {} + current_movie_art = 0 + current_tv_art = 0 + current_music_art = 0 + current_global_art = 0 + current_item_art = 0 + linksLoaded = False + logLevel = 0 + currentFilteredIndex = {} + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + self.getString = self.addonSettings.getLocalizedString + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C BackgroundRotationThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C BackgroundRotationThread -> " + msg) + + def run(self): + try: + self.run_internal() + except Exception, e: + xbmcgui.Dialog().ok(self.getString(30203), str(e)) + raise + + def run_internal(self): + self.logMsg("Started") + + try: + self.loadLastBackground() + except Exception, e: + self.logMsg("loadLastBackground Exception : " + str(e), level=0) + + WINDOW = xbmcgui.Window( 10000 ) + filterOnParent_Last = WINDOW.getProperty("MB3.Background.Collection") + + last_id = "" + self.updateArtLinks() + #self.setBackgroundLink(filterOnParent_Last) + lastRun = datetime.today() + itemLastRun = datetime.today() + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + + backgroundRefresh = int(addonSettings.getSetting('backgroundRefresh')) + if(backgroundRefresh < 10): + backgroundRefresh = 10 + + itemBackgroundRefresh = 5 + lastUserName = addonSettings.getSetting('username') + + while (xbmc.abortRequested == False): + td = datetime.today() - lastRun + td2 = datetime.today() - itemLastRun + secTotal = td.seconds + secTotal2 = td2.seconds + + userName = addonSettings.getSetting('username') + self.logMsg("Server details string : (" + userName + ") (" + lastUserName + ")", level=2) + + Collection = WINDOW.getProperty("MB3.Background.Collection") + if(secTotal > backgroundRefresh or filterOnParent_Last != Collection or userName != lastUserName): + lastUserName = userName + if(self.linksLoaded == False): + self.updateArtLinks() + lastRun = datetime.today() + filterOnParent_Last = Collection + backgroundRefresh = int(addonSettings.getSetting('backgroundRefresh')) + self.setBackgroundLink(Collection) + if(backgroundRefresh < 10): + backgroundRefresh = 10 + + # update item BG every 7 seconds + if(secTotal2 > itemBackgroundRefresh): + self.setItemBackgroundLink() + itemLastRun = datetime.today() + + # update item BG on selected item changes + if xbmc.getInfoLabel('ListItem.Property(id)') != None: + current_id = xbmc.getInfoLabel('ListItem.Property(id)') + elif xbmc.getInfoLabel('ListItem.Property(ItemGUID)') != None: + current_id=xbmc.getInfoLabel('ListItem.Property(ItemGUID)') + else: + current_id = '' + if current_id != last_id: + self.setItemBackgroundLink() + itemLastRun = datetime.today() + last_id = current_id + + xbmc.sleep(1000) + + try: + self.saveLastBackground() + except Exception, e: + self.logMsg("saveLastBackground Exception : " + str(e), level=0) + + self.logMsg("Exited") + + def loadLastBackground(self): + + __addon__ = xbmcaddon.Addon(id='plugin.video.xbmb3c') + __addondir__ = xbmc.translatePath( __addon__.getAddonInfo('profile') ) + + lastDataPath = __addondir__ + "LastBgLinks.json" + dataFile = open(lastDataPath, 'r') + jsonData = dataFile.read() + dataFile.close() + + self.logMsg(jsonData) + result = json.loads(jsonData) + + WINDOW = xbmcgui.Window( 10000 ) + if(result.get("global") != None): + WINDOW.setProperty("MB3.Background.Global.FanArt", result.get("global")["url"]) + self.logMsg("MB3.Background.Global.FanArt=" + result.get("global")["url"], level=2) + WINDOW.setProperty("MB3.Background.Global.FanArt.Poster", result.get("global")["poster"]) + self.logMsg("MB3.Background.Global.FanArt.Poster=" + result.get("global")["poster"], level=2) + WINDOW.setProperty("MB3.Background.Global.FanArt.Action", result.get("global")["action"]) + self.logMsg("MB3.Background.Global.FanArt.Action=" + result.get("global")["action"], level=2) + + if(result.get("movie") != None): + self.logMsg("Setting Movie Last : " + str(result.get("movie")), level=2) + WINDOW.setProperty("MB3.Background.Movie.FanArt", result.get("movie")["url"]) + + if(result.get("tv") != None): + self.logMsg("Setting TV Last : " + str(result.get("tv")), level=2) + WINDOW.setProperty("MB3.Background.TV.FanArt", result.get("tv")["url"]) + + if(result.get("music") != None): + self.logMsg("Setting Music Last : " + str(result.get("music")), level=2) + WINDOW.setProperty("MB3.Background.Music.FanArt", result.get("music")["url"]) + + def saveLastBackground(self): + + data = {} + if(len(self.global_art_links) > 0): + data["global"] = self.global_art_links[self.current_global_art] + if(len(self.movie_art_links) > 0): + data["movie"] = self.movie_art_links[self.current_movie_art] + if(len(self.tv_art_links) > 0): + data["tv"] = self.tv_art_links[self.current_tv_art] + if(len(self.music_art_links) > 0): + data["music"] = self.music_art_links[self.current_music_art] + + __addon__ = xbmcaddon.Addon(id='plugin.video.xbmb3c') + __addondir__ = xbmc.translatePath( __addon__.getAddonInfo('profile') ) + + lastDataPath = __addondir__ + "LastBgLinks.json" + dataFile = open(lastDataPath, 'w') + stringdata = json.dumps(data) + self.logMsg("Last Background Links : " + stringdata) + dataFile.write(stringdata) + dataFile.close() + + def setBackgroundLink(self, filterOnParent): + + WINDOW = xbmcgui.Window( 10000 ) + + if(len(self.movie_art_links) > 0): + self.logMsg("setBackgroundLink index movie_art_links " + str(self.current_movie_art + 1) + " of " + str(len(self.movie_art_links)), level=2) + artUrl = self.movie_art_links[self.current_movie_art]["url"] + WINDOW.setProperty("MB3.Background.Movie.FanArt", artUrl) + self.logMsg("MB3.Background.Movie.FanArt=" + artUrl) + self.current_movie_art = self.current_movie_art + 1 + if(self.current_movie_art == len(self.movie_art_links)): + self.current_movie_art = 0 + + if(len(self.tv_art_links) > 0): + self.logMsg("setBackgroundLink index tv_art_links " + str(self.current_tv_art + 1) + " of " + str(len(self.tv_art_links)), level=2) + artUrl = self.tv_art_links[self.current_tv_art]["url"] + WINDOW.setProperty("MB3.Background.TV.FanArt", artUrl) + self.logMsg("MB3.Background.TV.FanArt=" + artUrl) + self.current_tv_art = self.current_tv_art + 1 + if(self.current_tv_art == len(self.tv_art_links)): + self.current_tv_art = 0 + + if(len(self.music_art_links) > 0): + self.logMsg("setBackgroundLink index music_art_links " + str(self.current_music_art + 1) + " of " + str(len(self.music_art_links)), level=2) + artUrl = self.music_art_links[self.current_music_art]["url"] + WINDOW.setProperty("MB3.Background.Music.FanArt", artUrl) + self.logMsg("MB3.Background.Music.FanArt=" + artUrl) + self.current_music_art = self.current_music_art + 1 + if(self.current_music_art == len(self.music_art_links)): + self.current_music_art = 0 + + if(len(self.global_art_links) > 0): + self.logMsg("setBackgroundLink index global_art_links " + str(self.current_global_art + 1) + " of " + str(len(self.global_art_links)), level=2) + + next, nextItem = self.findNextLink(self.global_art_links, self.current_global_art, filterOnParent) + #nextItem = self.global_art_links[self.current_global_art] + self.current_global_art = next + + backGroundUrl = nextItem["url"] + posterUrl = nextItem["poster"] + actionUrl = nextItem["action"] + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + selectAction = addonSettings.getSetting('selectAction') + if(selectAction == "1"): + actionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?id=" + nextItem["id"] + "&mode=17)" + else: + actionUrl = nextItem["action"] + + WINDOW.setProperty("MB3.Background.Global.FanArt", backGroundUrl) + self.logMsg("MB3.Background.Global.FanArt=" + backGroundUrl) + WINDOW.setProperty("MB3.Background.Global.FanArt.Poster", posterUrl) + self.logMsg("MB3.Background.Global.FanArt.Poster=" + posterUrl) + WINDOW.setProperty("MB3.Background.Global.FanArt.Action", actionUrl) + self.logMsg("MB3.Background.Global.FanArt.Action=" + actionUrl) + + + def findNextLink(self, linkList, startIndex, filterOnParent): + + if(filterOnParent == None or filterOnParent == ""): + filterOnParent = "empty" + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + backgroundRefresh = int(addonSettings.getSetting('backgroundRefresh')) + if(backgroundRefresh < 10): + backgroundRefresh = 10 + + # first check the cache if we are filtering + if(self.currentFilteredIndex.get(filterOnParent) != None): + cachedItem = self.currentFilteredIndex.get(filterOnParent) + self.logMsg("filterOnParent=existing=" + filterOnParent + "=" + str(cachedItem)) + cachedIndex = cachedItem[0] + dateStamp = cachedItem[1] + td = datetime.today() - dateStamp + secTotal = td.seconds + if(secTotal < backgroundRefresh): + # use the cached background index + self.logMsg("filterOnParent=using=" + filterOnParent + "=" + str(secTotal)) + return (cachedIndex, linkList[cachedIndex]) + + currentIndex = startIndex + + isParentMatch = False + + #xbmc.log("findNextLink : filterOnParent=" + str(filterOnParent) + " isParentMatch=" + str(isParentMatch)) + + while(isParentMatch == False): + + currentIndex = currentIndex + 1 + + if(currentIndex == len(linkList)): + currentIndex = 0 + + if(currentIndex == startIndex): + return (currentIndex, linkList[currentIndex]) # we checked everything and nothing was ok so return the first one again + + isParentMatch = True + # if filter on not empty then make sure we have a bg from the correct collection + if(filterOnParent != "empty"): + isParentMatch = filterOnParent in linkList[currentIndex]["collections"] + + # save the cached index + cachedItem = [currentIndex, datetime.today()] + self.logMsg("filterOnParent=adding=" + filterOnParent + "=" + str(cachedItem)) + self.currentFilteredIndex[filterOnParent] = cachedItem + + nextIndex = currentIndex + 1 + + if(nextIndex == len(linkList)): + nextIndex = 0 + + return (nextIndex, linkList[currentIndex]) + + def updateArtLinks(self): + t1 = time.time() + result01 = self.updateCollectionArtLinks() + t2 = time.time() + result02 = self.updateTypeArtLinks() + t3 = time.time() + diff = t2 - t1 + xbmc.log("TIMEDIFF01 : " + str(diff)) + diff = t3 - t2 + xbmc.log("TIMEDIFF02 : " + str(diff)) + + if(result01 and result02): + xbmc.log("BackgroundRotationThread Update Links Worked") + self.linksLoaded = True + else: + xbmc.log("BackgroundRotationThread Update Links Failed") + self.linksLoaded = False + + + def updateActionUrls(self): + xbmc.log("BackgroundRotationThread updateActionUrls Called") + WINDOW = xbmcgui.Window( 10000 ) + + for x in range(0, 10): + contentUrl = WINDOW.getProperty("xbmb3c_collection_menuitem_content_" + str(x)) + if(contentUrl != None): + index = contentUrl.find("SessionId=(") + if(index > -1): + index = index + 11 + index2 = contentUrl.find(")", index+1) + timeNow = time.time() + newContentUrl = contentUrl[:index] + str(timeNow) + contentUrl[index2:] + xbmc.log("xbmb3c_collection_menuitem_content_" + str(x) + "=" + newContentUrl) + WINDOW.setProperty("xbmb3c_collection_menuitem_content_" + str(x), newContentUrl) + + def updateCollectionArtLinks(self): + self.logMsg("updateCollectionArtLinks Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + # get the user ID + userid = downloadUtils.getUserId() + self.logMsg("updateCollectionArtLinks UserID : " + userid) + + userUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/Root?format=json" + jsonData = downloadUtils.downloadUrl(userUrl, suppress=False, popup=1 ) + self.logMsg("updateCollectionArtLinks UserData : " + str(jsonData), 2) + result = json.loads(jsonData) + + parentid = result.get("Id") + self.logMsg("updateCollectionArtLinks ParentID : " + str(parentid), 2) + + userRootPath = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/items?ParentId=" + parentid + "&SortBy=SortName&Fields=CollectionType,Overview,RecursiveItemCount&format=json" + + jsonData = downloadUtils.downloadUrl(userRootPath, suppress=False, popup=1 ) + self.logMsg("updateCollectionArtLinks userRootPath : " + str(jsonData), 2) + result = json.loads(jsonData) + result = result.get("Items") + + artLinks = {} + collection_count = 0 + WINDOW = xbmcgui.Window( 10000 ) + + # process collections + for item in result: + + collectionType = item.get("CollectionType", "") + name = item.get("Name") + childCount = item.get("RecursiveItemCount") + self.logMsg("updateCollectionArtLinks Name : " + name, level=1) + self.logMsg("updateCollectionArtLinks RecursiveItemCount : " + str(childCount), level=1) + if(childCount == None or childCount == 0): + continue + + self.logMsg("updateCollectionArtLinks Processing Collection : " + name + " of type : " + collectionType, level=2) + + ##################################################################################################### + # Process collection item menu item + timeNow = time.time() + contentUrl = "plugin://plugin.video.xbmb3c?mode=16&ParentId=" + item.get("Id") + "&CollectionType=" + collectionType + "&SessionId=(" + str(timeNow) + ")" + actionUrl = ("ActivateWindow(VideoLibrary, plugin://plugin.video.xbmb3c/?mode=21&ParentId=" + item.get("Id") + "&Name=" + name + ",return)").encode('utf-8') + xbmc.log("COLLECTION actionUrl: " + actionUrl) + WINDOW.setProperty("xbmb3c_collection_menuitem_name_" + str(collection_count), name) + WINDOW.setProperty("xbmb3c_collection_menuitem_action_" + str(collection_count), actionUrl) + WINDOW.setProperty("xbmb3c_collection_menuitem_collection_" + str(collection_count), name) + WINDOW.setProperty("xbmb3c_collection_menuitem_content_" + str(collection_count), contentUrl) + ##################################################################################################### + + ##################################################################################################### + # Process collection item backgrounds + collectionUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/items?ParentId=" + item.get("Id") + "&IncludeItemTypes=Movie,Series,Episode,MusicArtist,Trailer,MusicVideo,Video&Fields=ParentId,Overview&Recursive=true&CollapseBoxSetItems=false&format=json" + + jsonData = downloadUtils.downloadUrl(collectionUrl, suppress=False, popup=1 ) + collectionResult = json.loads(jsonData) + + collectionResult = collectionResult.get("Items") + if(collectionResult == None): + collectionResult = [] + + for col_item in collectionResult: + + id = col_item.get("Id") + name = col_item.get("Name") + images = col_item.get("BackdropImageTags") + + if(images != None and len(images) > 0): + stored_item = artLinks.get(id) + + if(stored_item == None): + + stored_item = {} + collections = [] + collections.append(item.get("Name")) + stored_item["collections"] = collections + links = [] + images = col_item.get("BackdropImageTags") + parentID = col_item.get("ParentId") + name = col_item.get("Name") + if (images == None): + images = [] + index = 0 + + # build poster image link + posterImage = "" + actionUrl = "" + if(col_item.get("Type") == "Movie" or col_item.get("Type") == "Trailer" or col_item.get("Type") == "MusicVideo" or col_item.get("Type") == "Video"): + posterImage = downloadUtils.getArtwork(col_item, "Primary") + url = mb3Host + ":" + mb3Port + ',;' + id + url = urllib.quote(url) + #actionUrl = "ActivateWindow(VideoLibrary, plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + url + " ,return)" + actionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + url + ")" + + elif(col_item.get("Type") == "Series"): + posterImage = downloadUtils.getArtwork(col_item, "Primary") + actionUrl = "ActivateWindow(VideoLibrary, plugin://plugin.video.xbmb3c/?mode=21&ParentId=" + id + "&Name=" + name + ",return)" + plot = col_item.get("Overview") + for backdrop in images: + + info = {} + info["url"] = downloadUtils.getArtwork(col_item, "Backdrop", index=str(index)) + info["poster"] = posterImage + info["action"] = actionUrl + info["index"] = index + info["id"] = id + info["action"] = "None" + info["plot"] = plot + info["parent"] = parentID + info["name"] = name + links.append(info) + index = index + 1 + + stored_item["links"] = links + artLinks[id] = stored_item + else: + stored_item["collections"].append(item.get("Name")) + ##################################################################################################### + + collection_count = collection_count + 1 + + # build global link list + final_global_art = [] + + for id in artLinks: + item = artLinks.get(id) + collections = item.get("collections") + links = item.get("links") + + for link_item in links: + link_item["collections"] = collections + final_global_art.append(link_item) + #xbmc.log("COLLECTION_DATA GROUPS " + str(link_item)) + + self.global_art_links = final_global_art + random.shuffle(self.global_art_links) + self.logMsg("Background Global Art Links : " + str(len(self.global_art_links))) + + return True + + def updateTypeArtLinks(self): + self.logMsg("updateTypeArtLinks Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + # get the user ID + userid = downloadUtils.getUserId() + self.logMsg("updateTypeArtLinks UserID : " + userid) + + # load Movie BG + moviesUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Fields=ParentId,Overview&CollapseBoxSetItems=false&Recursive=true&IncludeItemTypes=Movie&format=json" + + jsonData = downloadUtils.downloadUrl(moviesUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + + result = result.get("Items") + if(result == None): + result = [] + + for item in result: + images = item.get("BackdropImageTags") + id = item.get("Id") + parentID = item.get("ParentId") + name = item.get("Name") + plot = item.get("Overview") + url = mb3Host + ":" + mb3Port + ',;' + id + url = urllib.quote(url) + actionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + url + ")" + if (images == None): + images = [] + index = 0 + + trailerActionUrl = None + if item.get("LocalTrailerCount") != None and item.get("LocalTrailerCount") > 0: + itemTrailerUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/" + id + "/LocalTrailers?format=json" + jsonData = downloadUtils.downloadUrl(itemTrailerUrl, suppress=False, popup=1 ) + trailerItem = json.loads(jsonData) + trailerUrl = mb3Host + ":" + mb3Port + ',;' + trailerItem[0].get("Id") + trailerUrl = urllib.quote(trailerUrl) + trailerActionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + trailerUrl + ")" + + for backdrop in images: + + info = {} + info["url"] = downloadUtils.getArtwork(item, "Backdrop", index=str(index)) + info["index"] = index + info["id"] = id + info["plot"] = plot + info["action"] = actionUrl + info["trailer"] = trailerActionUrl + info["parent"] = parentID + info["name"] = name + self.logMsg("BG Movie Image Info : " + str(info), level=2) + + if (info not in self.movie_art_links): + self.movie_art_links.append(info) + index = index + 1 + + random.shuffle(self.movie_art_links) + self.logMsg("Background Movie Art Links : " + str(len(self.movie_art_links))) + + # load TV BG links + tvUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Fields=ParentId,Overview&CollapseBoxSetItems=false&Recursive=true&IncludeItemTypes=Series&format=json" + + jsonData = downloadUtils.downloadUrl(tvUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + + result = result.get("Items") + if(result == None): + result = [] + + for item in result: + images = item.get("BackdropImageTags") + id = item.get("Id") + parentID = item.get("ParentId") + name = item.get("Name") + plot = item.get("Overview") + if (images == None): + images = [] + index = 0 + for backdrop in images: + + info = {} + info["url"] = downloadUtils.getArtwork(item, "Backdrop", index=str(index)) + info["index"] = index + info["id"] = id + info["action"] = "None" + info["trailer"] = "None" + info["plot"] = plot + info["parent"] = parentID + info["name"] = name + self.logMsg("BG TV Image Info : " + str(info), level=2) + + if (info not in self.tv_art_links): + self.tv_art_links.append(info) + index = index + 1 + + random.shuffle(self.tv_art_links) + self.logMsg("Background Tv Art Links : " + str(len(self.tv_art_links))) + + # load music BG links + musicUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Fields=ParentId,Overview&CollapseBoxSetItems=false&Recursive=true&IncludeItemTypes=MusicArtist&format=json" + + jsonData = downloadUtils.downloadUrl(musicUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + + result = result.get("Items") + if(result == None): + result = [] + + for item in result: + images = item.get("BackdropImageTags") + id = item.get("Id") + parentID = item.get("ParentId") + name = item.get("Name") + plot = item.get("Overview") + if (images == None): + images = [] + index = 0 + for backdrop in images: + + info = {} + info["url"] = downloadUtils.getArtwork(item, "Backdrop", index=str(index)) + info["index"] = index + info["id"] = id + info["action"] = "None" + info["trailer"] = "None" + info["plot"] = plot + info["parent"] = parentID + info["name"] = name + self.logMsg("BG Music Image Info : " + str(info), level=2) + + if (info not in self.music_art_links): + self.music_art_links.append(info) + index = index + 1 + + random.shuffle(self.music_art_links) + self.logMsg("Background Music Art Links : " + str(len(self.music_art_links))) + + # + # build a map indexed by id that contains a list of BG art for each item + # this is used for selected item BG rotation + # + self.item_art_links = {} + + # add movie BG links + for bg_item in self.movie_art_links: + item_id = bg_item["id"] + if(self.item_art_links.get(item_id) != None): + self.item_art_links[item_id].append(bg_item) + else: + bg_list = [] + bg_list.append(bg_item) + self.item_art_links[item_id] = bg_list + + # add TV BG links + for bg_item in self.tv_art_links: + item_id = bg_item["id"] + if(self.item_art_links.get(item_id) != None): + self.item_art_links[item_id].append(bg_item) + else: + bg_list = [] + bg_list.append(bg_item) + self.item_art_links[item_id] = bg_list + + # add music BG links + for bg_item in self.music_art_links: + item_id = bg_item["id"] + if(self.item_art_links.get(item_id) != None): + self.item_art_links[item_id].append(bg_item) + else: + bg_list = [] + bg_list.append(bg_item) + self.item_art_links[item_id] = bg_list + + + return True + + def setItemBackgroundLink(self): + + id = xbmc.getInfoLabel('ListItem.Property(ItemGUID)') + self.logMsg("setItemBackgroundLink ItemGUID : " + id, 1) + + WINDOW = xbmcgui.Window( 10000 ) + if id != None and id != "": + + listOfBackgrounds = self.item_art_links.get(id) + listOfData = self.item_art_links.get(xbmc.getInfoLabel('ListItem.Property(id)')) + + # if for some reson the item is not in the cache try to load it now + if(listOfBackgrounds == None or len(listOfBackgrounds) == 0): + self.loadItemBackgroundLinks(id) + if(listOfData == None or len(listOfData) == 0): + self.loadItemBackgroundLinks(xbmc.getInfoLabel('ListItem.Property(id)')) + + + listOfBackgrounds = self.item_art_links.get(id) + listOfData = self.item_art_links.get(xbmc.getInfoLabel('ListItem.Property(id)')) + + if listOfBackgrounds != None: + if listOfData != None: + if listOfData[0]["plot"] != "" and listOfData[0]["plot"] != None: + plot=listOfData[0]["plot"] + plot=plot.encode("utf-8") + WINDOW.setProperty("MB3.Plot", plot ) + else: + WINDOW.clearProperty("MB3.Plot") + + if listOfBackgrounds[0]["action"] != None and listOfBackgrounds[0]["action"] != "": + action=listOfBackgrounds[0]["action"] + WINDOW.setProperty("MB3.Action", action ) + else: + WINDOW.clearProperty("MB3.Action") + + if listOfBackgrounds[0].get("trailer") != None and listOfBackgrounds[0]["trailer"] != "": + trailerAction=listOfBackgrounds[0]["trailer"] + WINDOW.setProperty("MB3.TrailerAction", trailerAction ) + else: + WINDOW.clearProperty("MB3.TrailerAction") + + if(listOfBackgrounds != None and len(listOfBackgrounds) > 0): + self.logMsg("setItemBackgroundLink Image " + str(self.current_item_art + 1) + " of " + str(len(listOfBackgrounds)), 1) + try: + artUrl = listOfBackgrounds[self.current_item_art]["url"] + except IndexError: + self.current_item_art = 0 + artUrl = listOfBackgrounds[self.current_item_art]["url"] + + WINDOW.setProperty("MB3.Background.Item.FanArt", artUrl) + self.logMsg("setItemBackgroundLink MB3.Background.Item.FanArt=" + artUrl, 1) + + self.current_item_art = self.current_item_art + 1 + if(self.current_item_art == len(listOfBackgrounds) - 1): + self.current_item_art = 0 + + else: + self.logMsg("setItemBackgroundLink Resetting MB3.Background.Item.FanArt", 1) + WINDOW.clearProperty("MB3.Background.Item.FanArt") + + else: + self.logMsg("setItemBackgroundLink Resetting MB3.Background.Item.FanArt", 1) + WINDOW.clearProperty("MB3.Background.Item.FanArt") + WINDOW.clearProperty("MB3.Plot") + WINDOW.clearProperty("MB3.Action") + WINDOW.clearProperty("MB3.TrailerAction") + + + def loadItemBackgroundLinks(self, id): + + if(id == None or len(id) == 0): + self.logMsg("loadItemBackgroundLinks id was empty") + return + + self.logMsg("loadItemBackgroundLinks Called for id : " + id) + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + userid = downloadUtils.getUserId() + itemUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/" + id + "?Fields=ParentId,Overview&format=json" + + jsonData = downloadUtils.downloadUrl(itemUrl, suppress=False, popup=1 ) + item = json.loads(jsonData) + + self.logMsg("loadItemBackgroundLinks found item : " + str(item), 2); + + if(item == None): + item = [] + + #for item in result: + images = item.get("BackdropImageTags") + plot = item.get("Overview") + id = item.get("Id") + urlid = id + parentID = item.get("ParentId") + origid = id + name = item.get("Name") + if (images == None or images == []): + images = item.get("ParentBackdropImageTags") + urlid = item.get("ParentBackdropItemId") + if (images == None): + images = [] + + index = 0 + url = mb3Host + ":" + mb3Port + ',;' + id + url = urllib.quote(url) + actionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + url + ")" + trailerActionUrl = None + if item.get("LocalTrailerCount") != None and item.get("LocalTrailerCount") > 0: + itemTrailerUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/" + id + "/LocalTrailers?format=json" + jsonData = downloadUtils.downloadUrl(itemTrailerUrl, suppress=False, popup=1 ) + trailerItem = json.loads(jsonData) + trailerUrl = mb3Host + ":" + mb3Port + ',;' + trailerItem[0].get("Id") + trailerUrl = urllib.quote(trailerUrl) + trailerActionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + trailerUrl + ")" + + newBgLinks = [] + for backdrop in images: + info = {} + info["url"] = downloadUtils.getArtwork(item, "Backdrop", index=str(index)) + info["plot"] = plot + info["action"] = actionUrl + info["trailer"] = trailerActionUrl + info["index"] = index + info["id"] = urlid + info["parent"] = parentID + info["name"] = name + self.logMsg("BG Item Image Info : " + str(info), level=2) + newBgLinks.append(info) + index = index + 1 + + if(len(newBgLinks) > 0): + self.item_art_links[origid] = newBgLinks + + + diff --git a/resources/lib/ClientInformation.py b/resources/lib/ClientInformation.py new file mode 100644 index 0000000..9a759aa --- /dev/null +++ b/resources/lib/ClientInformation.py @@ -0,0 +1,11 @@ +from uuid import getnode as get_mac +import xbmcaddon + +class ClientInformation(): + + def getMachineId(self): + return "%012X"%get_mac() + + def getVersion(self): + version = xbmcaddon.Addon(id="plugin.video.xbmb3c").getAddonInfo("version") + return version diff --git a/resources/lib/DownloadUtils.py b/resources/lib/DownloadUtils.py new file mode 100644 index 0000000..7d5426a --- /dev/null +++ b/resources/lib/DownloadUtils.py @@ -0,0 +1,422 @@ +import xbmc +import xbmcgui +import xbmcaddon +import urllib +import urllib2 +import httplib +import requests +import hashlib +import StringIO +import gzip +import sys +import json as json +from random import randrange +from uuid import getnode as get_mac +from ClientInformation import ClientInformation + +class DownloadUtils(): + + logLevel = 0 + addonSettings = None + getString = None + + def __init__(self, *args): + self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + self.getString = self.addonSettings.getLocalizedString + level = self.addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C DownloadUtils -> " + msg) + + def getUserId(self): + + port = self.addonSettings.getSetting('port') + host = self.addonSettings.getSetting('ipaddress') + userName = self.addonSettings.getSetting('username') + + self.logMsg("Looking for user name: " + userName) + + jsonData = None + try: + jsonData = self.downloadUrl(host + ":" + port + "/mediabrowser/Users/Public?format=json") + except Exception, msg: + error = "Get User unable to connect to " + host + ":" + port + " : " + str(msg) + xbmc.log (error) + return "" + + + self.logMsg("GETUSER_JSONDATA_01:" + str(jsonData)) + + result = [] + + try: + result = json.loads(jsonData) + except Exception, e: + self.logMsg("jsonload : " + str(e) + " (" + jsonData + ")", level=1) + return "" + + self.logMsg("GETUSER_JSONDATA_02:" + str(result)) + + userid = "" + secure = False + for user in result: + if(user.get("Name") == userName): + userid = user.get("Id") + self.logMsg("Username Found:" + user.get("Name")) + if(user.get("HasPassword") == True): + secure = True + self.logMsg("Username Is Secure (HasPassword=True)") + break + + if(secure): + self.authenticate('http://' + host + ":" + port + "/mediabrowser/Users/AuthenticateByName?format=json") + + if userid == "": + return_value = xbmcgui.Dialog().ok(self.getString(30045),self.getString(30045)) + sys.exit() + + self.logMsg("userid : " + userid) + + WINDOW = xbmcgui.Window( 10000 ) + WINDOW.setProperty("userid", userid) + + return userid + + def getMachineId(self): + return "%012X"%get_mac() + + def authenticate(self, url): + txt_mac = self.getMachineId() + version = ClientInformation().getVersion() + + deviceName = self.addonSettings.getSetting('deviceName') + deviceName = deviceName.replace("\"", "_") + + authString = "Mediabrowser Client=\"XBMC\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" + headers = {'Accept-encoding': 'gzip', 'Authorization' : authString} + sha1 = hashlib.sha1(self.addonSettings.getSetting('password')) + resp = requests.post(url, data={'password':sha1.hexdigest(),'Username':self.addonSettings.getSetting('username')}, headers=headers) + code=str(resp.status_code) + result = resp.json() + if result.get("AccessToken") != self.addonSettings.getSetting('AccessToken'): + self.addonSettings.setSetting('AccessToken', result.get("AccessToken")) + if int(code) >= 200 and int(code)<300: + self.logMsg("User Authenticated") + else: + self.logMsg("User NOT Authenticated") + return_value = xbmcgui.Dialog().ok(self.getString(30044), self.getString(30044)) + sys.exit() + + def getArtwork(self, data, type, index = "0", userParentInfo = False): + + id = data.get("Id") + getSeriesData = False + + if type == "tvshow.poster": # Change the Id to the series to get the overall series poster + if data.get("Type") == "Season" or data.get("Type")== "Episode": + id = data.get("SeriesId") + getSeriesData = True + elif type == "poster" and data.get("Type") == "Episode" and self.addonSettings.getSetting('useSeasonPoster')=='true': # Change the Id to the Season to get the season poster + id = data.get("SeasonId") + if type == "poster" or type == "tvshow.poster": # Now that the Ids are right, change type to MB3 name + type="Primary" + if data.get("Type") == "Season": # For seasons: primary (poster), thumb and banner get season art, rest series art + if type != "Primary" and type != "Thumb" and type != "Banner": + id = data.get("SeriesId") + getSeriesData = True + if data.get("Type") == "Episode": # For episodes: primary (episode thumb) gets episode art, rest series art. + if type != "Primary": + id = data.get("SeriesId") + getSeriesData = True + + # if requested get parent info + if getSeriesData == True and userParentInfo == True: + self.logMsg("Using Parent Info for image link", level=1) + mb3Host = self.addonSettings.getSetting('ipaddress') + mb3Port = self.addonSettings.getSetting('port') + userid = self.getUserId() + seriesJsonData = self.downloadUrl("http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/" + id + "?format=json", suppress=False, popup=1 ) + seriesResult = json.loads(seriesJsonData) + data = seriesResult + + imageTag = "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format + originalType = type + if type == "Primary2" or type == "Primary3" or type=="SeriesPrimary": + type = "Primary" + if type == "Backdrop2" or type=="Backdrop3": + type = "Backdrop" + if type == "Thumb2" or type=="Thumb3": + type = "Thumb" + if(data.get("ImageTags") != None and data.get("ImageTags").get(type) != None): + imageTag = data.get("ImageTags").get(type) + + query = "" + height = "10000" + width = "10000" + played = "0" + + if self.addonSettings.getSetting('showIndicators')=='true': # add watched, unplayedcount and percentage played indicators to posters + + if (originalType =="Primary" or originalType =="Backdrop") and data.get("Type") != "Episode": + userData = data.get("UserData") + if originalType =="Backdrop" and index == "0": + totalbackdrops = len(data.get("BackdropImageTags")) + if totalbackdrops != 0: + index = str(randrange(0,totalbackdrops)) + if userData != None: + + UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount") + + if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true': + query = query + "&UnplayedCount=" + str(UnWatched) + + + if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'): + query = query + "&AddPlayedIndicator=true" + + PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage") + if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None : + PlayedPercentage = userData.get("PlayedPercentage") + if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true': + played = str(PlayedPercentage) + + elif originalType =="Primary2" and data.get("Type") != "Episode": + userData = data.get("UserData") + if userData != None: + + UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount") + + if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true': + query = query + "&UnplayedCount=" + str(UnWatched) + + if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'): + query = query + "&AddPlayedIndicator=true" + + PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage") + if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None : + PlayedPercentage = userData.get("PlayedPercentage") + if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true': + played = str(PlayedPercentage) + + height = "340" + width = "226" + + elif (originalType =="Primary3" and data.get("Type") != "Episode") or originalType == "SeriesPrimary": + userData = data.get("UserData") + if userData != None: + + UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount") + + if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true': + query = query + "&UnplayedCount=" + str(UnWatched) + + if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'): + query = query + "&AddPlayedIndicator=true" + + PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage") + if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None : + PlayedPercentage = userData.get("PlayedPercentage") + if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true': + played = str(PlayedPercentage) + + height = "800" + width = "550" + + elif type =="Primary" and data.get("Type") == "Episode": + userData = data.get("UserData") + if userData != None: + + UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount") + + if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true': + query = query + "&UnplayedCount=" + str(UnWatched) + + if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'): + query = query + "&AddPlayedIndicator=true" + + PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage") + if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None : + PlayedPercentage = userData.get("PlayedPercentage") + if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true': + played = str(PlayedPercentage) + + height = "410" + width = "770" + + elif originalType =="Backdrop2" or originalType =="Thumb2" and data.get("Type") != "Episode": + userData = data.get("UserData") + if originalType =="Backdrop2": + totalbackdrops = len(data.get("BackdropImageTags")) + if totalbackdrops != 0: + index = str(randrange(0,totalbackdrops)) + if userData != None: + + UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount") + + if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true': + query = query + "&UnplayedCount=" + str(UnWatched) + + if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'): + query = query + "&AddPlayedIndicator=true" + + PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage") + if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None : + PlayedPercentage = userData.get("PlayedPercentage") + if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true': + played = str(PlayedPercentage) + + height = "270" + width = "480" + + elif originalType =="Backdrop3" or originalType =="Thumb3" and data.get("Type") != "Episode": + userData = data.get("UserData") + if originalType =="Backdrop3": + totalbackdrops = len(data.get("BackdropImageTags")) + if totalbackdrops != 0: + index = str(randrange(0,totalbackdrops)) + if userData != None: + + UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount") + + if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true': + query = query + "&UnplayedCount=" + str(UnWatched) + + if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'): + query = query + "&AddPlayedIndicator=true" + + PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage") + if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None : + PlayedPercentage = userData.get("PlayedPercentage") + if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true': + played = str(PlayedPercentage) + + height = "800" + width = "1420" + + # use the local image proxy server that is made available by this addons service + + port = self.addonSettings.getSetting('port') + host = self.addonSettings.getSetting('ipaddress') + server = host + ":" + port + + artwork = "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + index + "/" + imageTag + "/original/" + height + "/" + width + "/" + played + "?" + query + if self.addonSettings.getSetting('disableCoverArt')=='true': + artwork = artwork + "&EnableImageEnhancers=false" + + self.logMsg("getArtwork : " + artwork, level=2) + + # do not return non-existing images + if ( (type!="Backdrop" and imageTag=="") | + (type=="Backdrop" and data.get("BackdropImageTags") != None and len(data.get("BackdropImageTags")) == 0) | + (type=="Backdrop" and data.get("BackdropImageTag") != None and len(data.get("BackdropImageTag")) == 0) + ): + if type=="Backdrop" and getSeriesData==True and data.get("ParentBackdropImageTags") == None: + artwork='' + + return artwork + + def getUserArtwork(self, data, type, index = "0"): + + id = data.get("Id") + #query = "&type=" + type + "&tag=" + imageTag + query = "" + height = "60" + width = "60" + played = "0" + + # use the local image proxy server that is made available by this addons service + port = self.addonSettings.getSetting('port') + host = self.addonSettings.getSetting('ipaddress') + server = host + ":" + port + + artwork = "http://" + server + "/mediabrowser/Users/" + str(id) + "/Images/Primary/0" + "?height=60&width=60&format=png" + + return artwork + + def imageUrl(self, id, type, index, width, height): + + port = self.addonSettings.getSetting('port') + host = self.addonSettings.getSetting('ipaddress') + server = host + ":" + port + + return "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + str(index) + "/e3ab56fe27d389446754d0fb04910a34/original/" + str(height) + "/" + str(width) + "/0" + + def downloadUrl(self, url, suppress=False, type="GET", popup=0 ): + self.logMsg("== ENTER: getURL ==") + try: + if url[0:4] == "http": + serversplit=2 + urlsplit=3 + else: + serversplit=0 + urlsplit=1 + + server=url.split('/')[serversplit] + urlPath="/"+"/".join(url.split('/')[urlsplit:]) + + self.logMsg("url = " + url) + self.logMsg("server = "+str(server), level=2) + self.logMsg("urlPath = "+str(urlPath), level=2) + conn = httplib.HTTPConnection(server, timeout=20) + #head = {"Accept-Encoding" : "gzip,deflate", "Accept-Charset" : "UTF-8,*"} + if self.addonSettings.getSetting('AccessToken')==None: + self.addonSettings.setSetting('AccessToken','') + head = {"Accept-Encoding" : "gzip", "Accept-Charset" : "UTF-8,*", "X-MediaBrowser-Token" : self.addonSettings.getSetting('AccessToken')} + #head = getAuthHeader() + conn.request(method=type, url=urlPath, headers=head) + #conn.request(method=type, url=urlPath) + data = conn.getresponse() + self.logMsg("GET URL HEADERS : " + str(data.getheaders()), level=2) + link = "" + contentType = "none" + if int(data.status) == 200: + retData = data.read() + contentType = data.getheader('content-encoding') + self.logMsg("Data Len Before : " + str(len(retData))) + if(contentType == "gzip"): + retData = StringIO.StringIO(retData) + gzipper = gzip.GzipFile(fileobj=retData) + link = gzipper.read() + else: + link = retData + + self.logMsg("Data Len After : " + str(len(link))) + self.logMsg("====== 200 returned =======") + self.logMsg("Content-Type : " + str(contentType)) + self.logMsg(link) + self.logMsg("====== 200 finished ======") + + elif ( int(data.status) == 301 ) or ( int(data.status) == 302 ): + try: conn.close() + except: pass + return data.getheader('Location') + + elif int(data.status) >= 400: + error = "HTTP response error: " + str(data.status) + " " + str(data.reason) + xbmc.log (error) + if suppress is False: + if popup == 0: + xbmc.executebuiltin("XBMC.Notification(URL error: "+ str(data.reason) +",)") + else: + xbmcgui.Dialog().ok(self.getString(30135),server) + xbmc.log (error) + try: conn.close() + except: pass + return "" + else: + link = "" + except Exception, msg: + error = "Unable to connect to " + str(server) + " : " + str(msg) + xbmc.log (error) + xbmc.executebuiltin("XBMC.Notification(\"XBMB3C\": URL error: Unable to connect to server,)") + xbmcgui.Dialog().ok("",self.getString(30204)) + raise + else: + try: conn.close() + except: pass + + return link \ No newline at end of file diff --git a/resources/lib/InProgressItems.py b/resources/lib/InProgressItems.py new file mode 100644 index 0000000..b615a47 --- /dev/null +++ b/resources/lib/InProgressItems.py @@ -0,0 +1,312 @@ +################################################################################################# +# In Progress Updater +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +import urllib +from DownloadUtils import DownloadUtils + +_MODE_BASICPLAY=12 + +#define our global download utils +downloadUtils = DownloadUtils() + +class InProgressUpdaterThread(threading.Thread): + + logLevel = 0 + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C InProgressUpdaterThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C InProgressUpdaterThread -> " + msg) + + def run(self): + self.logMsg("Started") + + self.updateInProgress() + lastRun = datetime.today() + + updateInterval = 300 + + while (xbmc.abortRequested == False): + td = datetime.today() - lastRun + secTotal = td.seconds + + if(secTotal > updateInterval): + self.updateInProgress() + lastRun = datetime.today() + + xbmc.sleep(3000) + + self.logMsg("Exited") + + def updateInProgress(self): + self.logMsg("updateInProgress Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + userid = downloadUtils.getUserId() + self.logMsg("InProgress UserName : " + userName + " UserID : " + userid) + + self.logMsg("Updating In Progress Movie List") + + recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=DatePlayed&SortOrder=Descending&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&Filters=IsResumable&IncludeItemTypes=Movie&format=json" + + jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + result = result.get("Items") + if(result == None): + result = [] + + WINDOW = xbmcgui.Window( 10000 ) + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + rating = item.get("CommunityRating") + criticrating = item.get("CriticRating") + officialrating = item.get("OfficialRating") + criticratingsummary = "" + if(item.get("CriticRatingSummary") != None): + criticratingsummary = item.get("CriticRatingSummary").encode('utf-8') + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + year = item.get("ProductionYear") + if(item.get("RunTimeTicks") != None): + runtime = str(int(item.get("RunTimeTicks"))/(10000000*60)) + else: + runtime = "0" + + userData = item.get("UserData") + if(userData != None): + reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000 + seekTime = reasonableTicks / 10000 + duration = float(runtime) + resume = float(seekTime) / 60.0 + if (duration == 0): + percentage=0 + else: + percentage = (resume / duration) * 100.0 + perasint = int(percentage) + title = str(perasint) + "% " + title + + item_id = item.get("Id") + thumbnail = downloadUtils.getArtwork(item, "Primary2") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + medium_fanart = downloadUtils.getArtwork(item, "Backdrop3") + + if item.get("ImageTags").get("Thumb") != None: + realthumbnail = downloadUtils.getArtwork(item, "Thumb3") + else: + realthumbnail = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Title = " + title, level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Thumb = " + realthumbnail, level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Plot = " + plot, level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Year = " + str(year), level=2) + self.logMsg("InProgressMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2) + + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Title", title) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Thumb", realthumbnail) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Art(fanart)", fanart) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Art(medium_fanart)", medium_fanart) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Art(clearlogo)", logo) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Art(poster)", thumbnail) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Rating", str(rating)) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Mpaa", str(officialrating)) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".CriticRating", str(criticrating)) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Plot", plot) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Year", str(year)) + WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Runtime", str(runtime)) + + WINDOW.setProperty("InProgressMovieMB3.Enabled", "true") + + item_count = item_count + 1 + + # blank any not available + for x in range(item_count, 11): + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Title", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Thumb", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Path", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Art(fanart)", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Art(clearlogo)", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Art(poster)", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Rating", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".CriticRating", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".CriticRatingSummary", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Plot", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Year", "") + WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Runtime", "") + + + #Updating Recent TV Show List + self.logMsg("Updating In Progress Episode List") + + recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=DatePlayed&SortOrder=Descending&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&Filters=IsResumable&IncludeItemTypes=Episode&format=json" + + jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + + result = result.get("Items") + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + seriesName = "Missing Name" + if(item.get("SeriesName") != None): + seriesName = item.get("SeriesName").encode('utf-8') + + eppNumber = "X" + tempEpisodeNumber = "00" + if(item.get("IndexNumber") != None): + eppNumber = item.get("IndexNumber") + if eppNumber < 10: + tempEpisodeNumber = "0" + str(eppNumber) + else: + tempEpisodeNumber = str(eppNumber) + + seasonNumber = item.get("ParentIndexNumber") + if seasonNumber < 10: + tempSeasonNumber = "0" + str(seasonNumber) + else: + tempSeasonNumber = str(seasonNumber) + rating = str(item.get("CommunityRating")) + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + + if(item.get("RunTimeTicks") != None): + runtime = str(int(item.get("RunTimeTicks"))/(10000000*60)) + else: + runtime = "0" + + userData = item.get("UserData") + if(userData != None): + reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000 + seekTime = reasonableTicks / 10000 + duration = float(runtime) + resume = float(seekTime) / 60.0 + if (duration == 0): + percentage=0 + else: + percentage = (resume / duration) * 100.0 + perasint = int(percentage) + title = str(perasint) + "% " + title + + item_id = item.get("Id") + + if item.get("Type") == "Episode" or item.get("Type") == "Season": + series_id = item.get("SeriesId") + + poster = downloadUtils.getArtwork(item, "SeriesPrimary") + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + medium_fanart = downloadUtils.getArtwork(item, "Backdrop3") + + banner = downloadUtils.getArtwork(item, "Banner") + if item.get("SeriesThumbImageTag") != None: + seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3") + else: + seriesthumbnail = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2) + self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2) + + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".EpisodeTitle", title) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Rating", rating) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.medium_fanart)", medium_fanart) + + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster) + WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Plot", plot) + + WINDOW.setProperty("InProgresstEpisodeMB3.Enabled", "true") + + item_count = item_count + 1 + + # blank any not available + for x in range(item_count, 11): + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".EpisodeTitle", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".ShowTitle", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".EpisodeNo", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".SeasonNo", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Thumb", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Path", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Rating", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Art(tvshow.fanart)", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Art(tvshow.clearlogo)", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Art(tvshow.banner)", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Art(tvshow.poster)", "") + WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Plot", "") + + + \ No newline at end of file diff --git a/resources/lib/InfoUpdater.py b/resources/lib/InfoUpdater.py new file mode 100644 index 0000000..f7c21de --- /dev/null +++ b/resources/lib/InfoUpdater.py @@ -0,0 +1,250 @@ +################################################################################################# +# Info Updater +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +import urllib +from DownloadUtils import DownloadUtils + +_MODE_BASICPLAY=12 + +#define our global download utils +downloadUtils = DownloadUtils() + +class InfoUpdaterThread(threading.Thread): + + logLevel = 0 + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C InfoUpdaterThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C InfoUpdaterThread -> " + msg) + + def run(self): + self.logMsg("Started") + + self.updateInfo() + lastRun = datetime.today() + + while (xbmc.abortRequested == False): + td = datetime.today() - lastRun + secTotal = td.seconds + + if(secTotal > 300): + self.updateInfo() + lastRun = datetime.today() + + xbmc.sleep(3000) + + self.logMsg("Exited") + + def updateInfo(self): + self.logMsg("updateInfo Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + userid = downloadUtils.getUserId() + self.logMsg("updateInfo UserID : " + userid) + + self.logMsg("Updating info List") + + infoUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Fields=CollectionType&format=json" + + jsonData = downloadUtils.downloadUrl(infoUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + + result = result.get("Items") + WINDOW = xbmcgui.Window( 10000 ) + if(result == None): + result = [] + + item_count = 1 + movie_count = 0 + movie_unwatched_count = 0 + tv_count = 0 + episode_count = 0 + episode_unwatched_count = 0 + tv_unwatched_count = 0 + music_count = 0 + music_songs_count = 0 + music_songs_unplayed_count = 0 + musicvideos_count = 0 + musicvideos_unwatched_count = 0 + trailers_count = 0 + trailers_unwatched_count = 0 + photos_count = 0 + channels_count = 0 + for item in result: + collectionType = item.get("CollectionType") + if collectionType==None: + collectionType="unknown" + self.logMsg("collectionType " + collectionType) + userData = item.get("UserData") + if(collectionType == "movies"): + movie_count = movie_count + item.get("RecursiveItemCount") + movie_unwatched_count = movie_unwatched_count + userData.get("UnplayedItemCount") + + if(collectionType == "musicvideos"): + musicvideos_count = musicvideos_count + item.get("RecursiveItemCount") + musicvideos_unwatched_count = musicvideos_unwatched_count + userData.get("UnplayedItemCount") + + if(collectionType == "tvshows"): + tv_count = tv_count + item.get("ChildCount") + episode_count = episode_count + item.get("RecursiveItemCount") + episode_unwatched_count = episode_unwatched_count + userData.get("UnplayedItemCount") + + if(collectionType == "music"): + music_count = music_count + item.get("ChildCount") + music_songs_count = music_songs_count + item.get("RecursiveItemCount") + music_songs_unplayed_count = music_songs_unplayed_count + userData.get("UnplayedItemCount") + + if(collectionType == "photos"): + photos_count = photos_count + item.get("RecursiveItemCount") + + if(item.get("Name") == "Trailers"): + trailers_count = trailers_count + item.get("RecursiveItemCount") + trailers_unwatched_count = trailers_unwatched_count + userData.get("UnplayedItemCount") + + self.logMsg("MoviesCount " + str(movie_count), level=2) + self.logMsg("MoviesUnWatchedCount " + str(movie_unwatched_count), level=2) + self.logMsg("MusicVideosCount " + str(musicvideos_count), level=2) + self.logMsg("MusicVideosUnWatchedCount " + str(musicvideos_unwatched_count), level=2) + self.logMsg("TVCount " + str(tv_count), level=2) + self.logMsg("EpisodeCount " + str(episode_count), level=2) + self.logMsg("EpisodeUnWatchedCount " + str(episode_unwatched_count), level=2) + self.logMsg("MusicCount " + str(music_count), level=2) + self.logMsg("SongsCount " + str(music_songs_count), level=2) + self.logMsg("SongsUnPlayedCount " + str(music_songs_unplayed_count), level=2) + self.logMsg("TrailersCount" + str(trailers_count), level=2) + self.logMsg("TrailersUnWatchedCount" + str(trailers_unwatched_count), level=2) + self.logMsg("PhotosCount" + str(photos_count), level=2) + + #item_count = item_count + 1 + + movie_watched_count = movie_count - movie_unwatched_count + musicvideos_watched_count = musicvideos_count - musicvideos_unwatched_count + episode_watched_count = episode_count - episode_unwatched_count + music_songs_played_count = music_songs_count - music_songs_unplayed_count + trailers_watched_count = trailers_count - trailers_unwatched_count + WINDOW.setProperty("MB3TotalMovies", str(movie_count)) + WINDOW.setProperty("MB3TotalUnWatchedMovies", str(movie_unwatched_count)) + WINDOW.setProperty("MB3TotalWatchedMovies", str(movie_watched_count)) + WINDOW.setProperty("MB3TotalMusicVideos", str(musicvideos_count)) + WINDOW.setProperty("MB3TotalUnWatchedMusicVideos", str(musicvideos_unwatched_count)) + WINDOW.setProperty("MB3TotalWatchedMusicVideos", str(musicvideos_watched_count)) + WINDOW.setProperty("MB3TotalTvShows", str(tv_count)) + WINDOW.setProperty("MB3TotalEpisodes", str(episode_count)) + WINDOW.setProperty("MB3TotalUnWatchedEpisodes", str(episode_unwatched_count)) + WINDOW.setProperty("MB3TotalWatchedEpisodes", str(episode_watched_count)) + WINDOW.setProperty("MB3TotalMusicAlbums", str(music_count)) + WINDOW.setProperty("MB3TotalMusicSongs", str(music_songs_count)) + WINDOW.setProperty("MB3TotalUnPlayedMusicSongs", str(music_songs_unplayed_count)) + WINDOW.setProperty("MB3TotalPlayedMusicSongs", str(music_songs_played_count)) + WINDOW.setProperty("MB3TotalTrailers", str(trailers_count)) + WINDOW.setProperty("MB3TotalUnWatchedTrailers", str(trailers_unwatched_count)) + WINDOW.setProperty("MB3TotalWatchedTrailers", str(trailers_watched_count)) + WINDOW.setProperty("MB3TotalPhotos", str(photos_count)) + + userUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "?format=json" + jsonData = downloadUtils.downloadUrl(userUrl, suppress=False, popup=1 ) + + result = json.loads(jsonData) + userImage = downloadUtils.getUserArtwork(result, "Primary") + WINDOW.setProperty("MB3UserImage", userImage) + xbmc.log("XBMB3C MB3UserImage -> " + userImage) + self.logMsg("InfoTV start") + infoTVUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?&IncludeItemTypes=Series&Recursive=true&SeriesStatus=Continuing&format=json" + + jsonData = downloadUtils.downloadUrl(infoTVUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("InfoTV Json Data : " + str(result), level=2) + + totalRunning = result.get("TotalRecordCount") + self.logMsg("TotalRunningCount " + str(totalRunning)) + WINDOW.setProperty("MB3TotalRunningTvShows", str(totalRunning)) + + self.logMsg("InfoNextAired start") + InfoNextAiredUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?IsUnaired=true&SortBy=PremiereDate%2CAirTime%2CSortName&SortOrder=Ascending&IncludeItemTypes=Episode&Limit=1&Recursive=true&Fields=SeriesInfo%2CUserData&format=json" + + jsonData = downloadUtils.downloadUrl(InfoNextAiredUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("InfoNextAired Json Data : " + str(result), level=2) + + result = result.get("Items") + if(result == None): + result = [] + + episode = "" + for item in result: + title = "" + seriesName = "" + if(item.get("SeriesName") != None): + seriesName = item.get("SeriesName").encode('utf-8') + + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + eppNumber = "" + tempEpisodeNumber = "" + if(item.get("IndexNumber") != None): + eppNumber = item.get("IndexNumber") + if eppNumber < 10: + tempEpisodeNumber = "0" + str(eppNumber) + else: + tempEpisodeNumber = str(eppNumber) + + seasonNumber = item.get("ParentIndexNumber") + if seasonNumber < 10: + tempSeasonNumber = "0" + str(seasonNumber) + else: + tempSeasonNumber = str(seasonNumber) + + episode = seriesName + " - " + title + " - S" + tempSeasonNumber + "E" + tempEpisodeNumber + + self.logMsg("MB3NextAiredEpisode" + episode) + WINDOW.setProperty("MB3NextAiredEpisode", episode) + self.logMsg("InfoNextAired end") + + today = datetime.today() + dateformat = today.strftime("%Y-%m-%d") + nextAiredUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?IsUnaired=true&SortBy=PremiereDate%2CAirTime%2CSortName&SortOrder=Ascending&IncludeItemTypes=Episode&Recursive=true&Fields=SeriesInfo%2CUserData&MinPremiereDate=" + str(dateformat) + "&MaxPremiereDate=" + str(dateformat) + "&format=json" + + jsonData = downloadUtils.downloadUrl(nextAiredUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("InfoNextAired total url: " + nextAiredUrl) + self.logMsg("InfoNextAired total Json Data : " + str(result), level=2) + + totalToday = result.get("TotalRecordCount") + self.logMsg("MB3NextAiredTotalToday " + str(totalToday)) + WINDOW.setProperty("MB3NextAiredTotalToday", str(totalToday)) + + self.logMsg("Channels start") + channelsUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Channels/?format=json" + + jsonData = downloadUtils.downloadUrl(channelsUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Channels Json Data : " + str(result), level=2) + + totalChannels = result.get("TotalRecordCount") + self.logMsg("TotalChannels " + str(totalRunning)) + WINDOW.setProperty("MB3TotalChannels", str(totalChannels)) diff --git a/resources/lib/ItemInfo.py b/resources/lib/ItemInfo.py new file mode 100644 index 0000000..47361fa --- /dev/null +++ b/resources/lib/ItemInfo.py @@ -0,0 +1,227 @@ + +import sys +import xbmc +import xbmcgui +import xbmcaddon +import json as json +import urllib +from DownloadUtils import DownloadUtils + +_MODE_BASICPLAY=12 +_MODE_CAST_LIST=14 +_MODE_PERSON_DETAILS=15 + +class ItemInfo(xbmcgui.WindowXMLDialog): + + id = "" + playUrl = "" + + def __init__(self, *args, **kwargs): + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + xbmc.log("WINDOW INITIALISED") + + def onInit(self): + self.action_exitkeys_id = [10, 13] + + __settings__ = xbmcaddon.Addon(id='plugin.video.xbmb3c') + port = __settings__.getSetting('port') + host = __settings__.getSetting('ipaddress') + server = host + ":" + port + + downloadUtils = DownloadUtils() + + userid = downloadUtils.getUserId() + + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + self.id + "?format=json", suppress=False, popup=1 ) + item = json.loads(jsonData) + + id = item.get("Id") + name = item.get("Name") + image = downloadUtils.getArtwork(item, "Primary") + fanArt = downloadUtils.getArtwork(item, "Backdrop") + + # calculate the percentage complete + userData = item.get("UserData") + cappedPercentage = None + if(userData != None): + playBackTicks = float(userData.get("PlaybackPositionTicks")) + if(playBackTicks != None and playBackTicks > 0): + runTimeTicks = float(item.get("RunTimeTicks", "0")) + if(runTimeTicks > 0): + percentage = int((playBackTicks / runTimeTicks) * 100.0) + cappedPercentage = percentage - (percentage % 10) + if(cappedPercentage == 0): + cappedPercentage = 10 + if(cappedPercentage == 100): + cappedPercentage = 90 + + episodeInfo = "" + type = item.get("Type") + if(type == "Episode" or type == "Season"): + name = item.get("SeriesName") + ": " + name + season = str(item.get("ParentIndexNumber")).zfill(2) + episodeNum = str(item.get("IndexNumber")).zfill(2) + episodeInfo = "S" + season + "xE" + episodeNum + + url = server + ',;' + id + url = urllib.quote(url) + self.playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + + self.peopleUrl = "XBMC.Container.Update(plugin://plugin.video.xbmb3c?mode=" + str(_MODE_CAST_LIST) + "&id=" + id + ")" + #self.peopleUrl = "XBMC.RunPlugin(plugin://plugin.video.xbmb3c?mode=" + str(_MODE_CAST_LIST) + "&id=" + id + ")" + + # all all the media stream info + mediaList = self.getControl(3220) + + mediaStreams = item.get("MediaStreams") + if(mediaStreams != None): + for mediaStream in mediaStreams: + if(mediaStream.get("Type") == "Video"): + videocodec = mediaStream.get("Codec") + if(videocodec == "mpeg2video"): + videocodec = "mpeg2" + height = str(mediaStream.get("Height")) + width = str(mediaStream.get("Width")) + aspectratio = mediaStream.get("AspectRatio") + fr = mediaStream.get("RealFrameRate") + videoInfo = width + "x" + height + " " + videocodec + " " + str(round(fr, 2)) + listItem = xbmcgui.ListItem("Video:", videoInfo) + mediaList.addItem(listItem) + if(mediaStream.get("Type") == "Audio"): + audiocodec = mediaStream.get("Codec") + channels = mediaStream.get("Channels") + lang = mediaStream.get("Language") + audioInfo = audiocodec + " " + str(channels) + if(lang != None and len(lang) > 0 and lang != "und"): + audioInfo = audioInfo + " " + lang + listItem = xbmcgui.ListItem("Audio:", audioInfo) + mediaList.addItem(listItem) + if(mediaStream.get("Type") == "Subtitle"): + lang = mediaStream.get("Language") + codec = mediaStream.get("Codec") + subInfo = codec + if(lang != None and len(lang) > 0 and lang != "und"): + subInfo = subInfo + " " + lang + listItem = xbmcgui.ListItem("Sub:", subInfo) + mediaList.addItem(listItem) + + + #for x in range(0, 10): + # listItem = xbmcgui.ListItem("Test:", "Test 02 " + str(x)) + # mediaList.addItem(listItem) + + # add overview + overview = item.get("Overview") + self.getControl(3223).setText(overview) + + # add people + peopleList = self.getControl(3230) + people = item.get("People") + + for person in people: + displayName = person.get("Name") + role = person.get("Role") + id = person.get("Id") + tag = person.get("PrimaryImageTag") + + baseName = person.get("Name") + baseName = baseName.replace(" ", "+") + baseName = baseName.replace("&", "_") + baseName = baseName.replace("?", "_") + baseName = baseName.replace("=", "_") + + actionUrl = "plugin://plugin.video.xbmb3c?mode=" + str(_MODE_PERSON_DETAILS) +"&name=" + baseName + + if(tag != None and len(tag) > 0): + thumbPath = downloadUtils.imageUrl(id, "Primary", 0, 400, 400) + listItem = xbmcgui.ListItem(label=displayName, label2=role, iconImage=thumbPath, thumbnailImage=thumbPath) + else: + listItem = xbmcgui.ListItem(label=displayName, label2=role) + + listItem.setProperty("ActionUrl", actionUrl) + peopleList.addItem(listItem) + + # add general info + infoList = self.getControl(3226) + listItem = xbmcgui.ListItem("Year:", str(item.get("ProductionYear"))) + infoList.addItem(listItem) + listItem = xbmcgui.ListItem("Rating:", str(item.get("CommunityRating"))) + infoList.addItem(listItem) + listItem = xbmcgui.ListItem("MPAA:", str(item.get("OfficialRating"))) + infoList.addItem(listItem) + duration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60)) + listItem = xbmcgui.ListItem("RunTime:", str(duration) + " Minutes") + infoList.addItem(listItem) + + genre = "" + genres = item.get("Genres") + if(genres != None): + for genre_string in genres: + if genre == "": #Just take the first genre + genre = genre_string + else: + genre = genre + " / " + genre_string + + listItem = xbmcgui.ListItem("Genre:", genre) + infoList.addItem(listItem) + + path = item.get('Path') + listItem = xbmcgui.ListItem("Path:", path) + infoList.addItem(listItem) + + # add resume percentage text to name + addResumePercent = __settings__.getSetting('addResumePercent') == 'true' + if (addResumePercent and cappedPercentage != None): + name = name + " (" + str(cappedPercentage) + "%)" + + self.getControl(3000).setLabel(name) + self.getControl(3003).setLabel(episodeInfo) + self.getControl(3001).setImage(fanArt) + + if(type == "Episode"): + self.getControl(3009).setImage(image) + if(cappedPercentage != None): + self.getControl(3010).setImage("Progress\progress_" + str(cappedPercentage) + ".png") + else: + self.getControl(3011).setImage(image) + if(cappedPercentage != None): + self.getControl(3012).setImage("Progress\progress_" + str(cappedPercentage) + ".png") + + # disable play button + if(type == "Season" or type == "Series"): + self.setFocusId(3226) + self.getControl(3002).setEnabled(False) + + def setId(self, id): + self.id = id + + def onFocus(self, controlId): + pass + + def doAction(self): + pass + + def closeDialog(self): + self.close() + + def onClick(self, controlID): + + if(controlID == 3002): + + # close all dialogs when playing an item + xbmc.executebuiltin("Dialog.Close(all,true)") + + xbmc.executebuiltin("RunPlugin(" + self.playUrl + ")") + self.close() + + elif(controlID == 3230): + + peopleList = self.getControl(3230) + item = peopleList.getSelectedItem() + action = item.getProperty("ActionUrl") + + xbmc.log(action) + xbmc.executebuiltin("RunPlugin(" + action + ")") + + pass + diff --git a/resources/lib/MenuLoad.py b/resources/lib/MenuLoad.py new file mode 100644 index 0000000..53a8e05 --- /dev/null +++ b/resources/lib/MenuLoad.py @@ -0,0 +1,116 @@ +################################################################################################# +# menu item loader thread +# this loads the favourites.xml and sets the windows props for the menus to auto display in skins +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import xml.etree.ElementTree as xml +import os +import threading + +class LoadMenuOptionsThread(threading.Thread): + + logLevel = 0 + addonSettings = None + getString = None + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + self.getString = self.addonSettings.getLocalizedString + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C LoadMenuOptionsThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C LoadMenuOptionsThread -> " + msg) + + def run(self): + try: + self.run_internal() + except Exception, e: + xbmcgui.Dialog().ok(self.getString(30205), str(e)) + raise + + def run_internal(self): + self.logMsg("LoadMenuOptionsThread Started") + + lastFavPath = "" + favourites_file = os.path.join(xbmc.translatePath('special://profile'), "favourites.xml") + self.loadMenuOptions(favourites_file) + lastFavPath = favourites_file + + try: + lastModLast = os.stat(favourites_file).st_mtime + except: + lastModLast = 0; + + while (xbmc.abortRequested == False): + + favourites_file = os.path.join(xbmc.translatePath('special://profile'), "favourites.xml") + try: + lastMod = os.stat(favourites_file).st_mtime + except: + lastMod = 0; + + if(lastFavPath != favourites_file or lastModLast != lastMod): + self.loadMenuOptions(favourites_file) + + lastFavPath = favourites_file + lastModLast = lastMod + + xbmc.sleep(3000) + + self.logMsg("LoadMenuOptionsThread Exited") + + def loadMenuOptions(self, pathTofavourites): + + self.logMsg("LoadMenuOptionsThread -> Loading menu items from : " + pathTofavourites) + WINDOW = xbmcgui.Window( 10000 ) + menuItem = 0 + + try: + tree = xml.parse(pathTofavourites) + rootElement = tree.getroot() + except Exception, e: + self.logMsg("LoadMenuOptionsThread -> Error Parsing favourites.xml : " + str(e), level=0) + for x in range(0, 10): + WINDOW.setProperty("xbmb3c_menuitem_name_" + str(x), "") + WINDOW.setProperty("xbmb3c_menuitem_action_" + str(x), "") + WINDOW.setProperty("xbmb3c_menuitem_collection_" + str(x), "") + return + + for child in rootElement.findall('favourite'): + name = child.get('name') + action = child.text + + if(len(name) > 1 and name[0:1] != '-'): + WINDOW.setProperty("xbmb3c_menuitem_name_" + str(menuItem), name) + WINDOW.setProperty("xbmb3c_menuitem_action_" + str(menuItem), action) + WINDOW.setProperty("xbmb3c_menuitem_collection_" + str(menuItem), name) + self.logMsg("xbmb3c_menuitem_name_" + str(menuItem) + " : " + name) + self.logMsg("xbmb3c_menuitem_action_" + str(menuItem) + " : " + action) + self.logMsg("xbmb3c_menuitem_collection_" + str(menuItem) + " : " + name) + + menuItem = menuItem + 1 + + for x in range(menuItem, menuItem+10): + WINDOW.setProperty("xbmb3c_menuitem_name_" + str(x), "") + WINDOW.setProperty("xbmb3c_menuitem_action_" + str(x), "") + self.logMsg("xbmb3c_menuitem_name_" + str(x) + " : ") + self.logMsg("xbmb3c_menuitem_action_" + str(x) + " : ") + self.logMsg("xbmb3c_menuitem_collection_" + str(x) + " : ") + + + + + \ No newline at end of file diff --git a/resources/lib/NextUpItems.py b/resources/lib/NextUpItems.py new file mode 100644 index 0000000..3e104d1 --- /dev/null +++ b/resources/lib/NextUpItems.py @@ -0,0 +1,196 @@ +################################################################################################# +# NextUp TV Updater +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +import urllib +from DownloadUtils import DownloadUtils + +_MODE_BASICPLAY=12 + +#define our global download utils +downloadUtils = DownloadUtils() + +class NextUpUpdaterThread(threading.Thread): + + logLevel = 0 + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C NextUpUpdaterThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C NextUpUpdaterThread -> " + msg) + + def run(self): + self.logMsg("Started") + + self.updateNextUp() + lastRun = datetime.today() + + while (xbmc.abortRequested == False): + td = datetime.today() - lastRun + secTotal = td.seconds + + if(secTotal > 300): + self.updateNextUp() + lastRun = datetime.today() + + xbmc.sleep(3000) + + self.logMsg("Exited") + + def updateNextUp(self): + self.logMsg("updateNextUp Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + userid = downloadUtils.getUserId() + self.logMsg("updateNextUp UserID : " + userid) + + self.logMsg("Updating NextUp List") + + nextUpUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Shows/NextUp?UserId=" + userid + "&Fields=Path,Genres,MediaStreams,Overview&format=json" + + jsonData = downloadUtils.downloadUrl(nextUpUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("NextUP TV Show Json Data : " + str(result), level=2) + + result = result.get("Items") + WINDOW = xbmcgui.Window( 10000 ) + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + seriesName = "Missing Name" + if(item.get("SeriesName") != None): + seriesName = item.get("SeriesName").encode('utf-8') + + eppNumber = "X" + tempEpisodeNumber = "XX" + if(item.get("IndexNumber") != None): + eppNumber = item.get("IndexNumber") + if eppNumber < 10: + tempEpisodeNumber = "0" + str(eppNumber) + else: + tempEpisodeNumber = str(eppNumber) + + seasonNumber = item.get("ParentIndexNumber") + tempSeasonNumber = "XX" + if seasonNumber < 10: + tempSeasonNumber = "0" + str(seasonNumber) + else: + tempSeasonNumber = str(seasonNumber) + rating = str(item.get("CommunityRating")) + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + + item_id = item.get("Id") + + poster = downloadUtils.getArtwork(item, "SeriesPrimary") + small_poster = downloadUtils.getArtwork(item, "Primary2") + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + medium_fanart = downloadUtils.getArtwork(item, "Backdrop3") + banner = downloadUtils.getArtwork(item, "Banner") + if item.get("SeriesThumbImageTag") != None: + seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3") + else: + seriesthumbnail = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + # Process UserData + userData = item.get("UserData") + if(userData != None): + resume = str(userData.get("PlaybackPositionTicks")) + if (resume == "0"): + resume = "False" + else: + resume = "True" + + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2) + self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Resume = " + resume, level=2) + + + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".EpisodeTitle", title) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Rating", rating) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.medium_fanart)", medium_fanart) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.small_poster)", small_poster) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Plot", plot) + WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Resume", resume) + + WINDOW.setProperty("NextUpEpisodeMB3.Enabled", "true") + + item_count = item_count + 1 + + if(item_count < 10): + # blank any not available + for x in range(item_count, 11): + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".EpisodeTitle", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".ShowTitle", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".EpisodeNo", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".SeasonNo", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Thumb", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Path", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Rating", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Art(tvshow.fanart)", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Art(tvshow.clearlogo)", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Art(tvshow.banner)", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Art(tvshow.poster)", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Plot", "") + WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Resume", "") + + + + diff --git a/resources/lib/PersonInfo.py b/resources/lib/PersonInfo.py new file mode 100644 index 0000000..610d884 --- /dev/null +++ b/resources/lib/PersonInfo.py @@ -0,0 +1,175 @@ + +import sys +import xbmc +import xbmcgui +import xbmcaddon +import json as json +import urllib +from DownloadUtils import DownloadUtils + +_MODE_GETCONTENT=0 +_MODE_ITEM_DETAILS=17 + +class PersonInfo(xbmcgui.WindowXMLDialog): + + pluginCastLink = "" + showMovies = False + personName = "" + + def __init__(self, *args, **kwargs): + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def onInit(self): + self.action_exitkeys_id = [10, 13] + + __settings__ = xbmcaddon.Addon(id='plugin.video.xbmb3c') + port = __settings__.getSetting('port') + host = __settings__.getSetting('ipaddress') + server = host + ":" + port + + downloadUtils = DownloadUtils() + + userid = downloadUtils.getUserId() + + jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Persons/" + self.personName + "?format=json", suppress=False, popup=1 ) + result = json.loads(jsonData) + + name = result.get("Name") + id = result.get("Id") + + # other lib items count + contentCounts = "" + if(result.get("AdultVideoCount") != None and result.get("AdultVideoCount") > 0): + contentCounts = contentCounts + "\nAdult Count : " + str(result.get("AdultVideoCount")) + if(result.get("MovieCount") != None and result.get("MovieCount") > 0): + contentCounts = contentCounts + "\nMovie Count : " + str(result.get("MovieCount")) + if(result.get("SeriesCount") != None and result.get("SeriesCount") > 0): + contentCounts = contentCounts + "\nSeries Count : " + str(result.get("SeriesCount")) + if(result.get("EpisodeCount") != None and result.get("EpisodeCount") > 0): + contentCounts = contentCounts + "\nEpisode Count : " + str(result.get("EpisodeCount")) + + if(len(contentCounts) > 0): + contentCounts = "Total Library Counts:" + contentCounts + + #overview + overview = "" + if(len(contentCounts) > 0): + overview = contentCounts + "\n\n" + over = result.get("Overview") + if(over == None or over == ""): + overview = overview + "No details available" + else: + overview = overview + over + + #person image + image = downloadUtils.getArtwork(result, "Primary") + + #get other movies + encoded = name.encode("utf-8") + encoded = urllib.quote(encoded) + url = "http://" + server + "/mediabrowser/Users/" + userid + "/Items/?Recursive=True&Person=" + encoded + "&format=json" + xbmc.log("URL: " + url) + jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1 ) + otherMovieResult = json.loads(jsonData) + + baseName = name.replace(" ", "+") + baseName = baseName.replace("&", "_") + baseName = baseName.replace("?", "_") + baseName = baseName.replace("=", "_") + + #detailsString = getDetailsString() + #search_url = "http://" + host + ":" + port + "/mediabrowser/Users/" + userid + "/Items/?Recursive=True&Person=PERSON_NAME&Fields=" + detailsString + "&format=json" + search_url = "http://" + host + ":" + port + "/mediabrowser/Users/" + userid + "/Items/?Recursive=True&Person=PERSON_NAME&format=json" + search_url = urllib.quote(search_url) + search_url = search_url.replace("PERSON_NAME", baseName) + self.pluginCastLink = "XBMC.Container.Update(plugin://plugin.video.xbmb3c?mode=" + str(_MODE_GETCONTENT) + "&url=" + search_url + ")" + + otherItemsList = None + try: + otherItemsList = self.getControl(3010) + + items = otherMovieResult.get("Items") + if(items == None): + items = [] + + for item in items: + item_id = item.get("Id") + item_name = item.get("Name") + + type_info = "" + image_id = item_id + item_type = item.get("Type") + + if(item_type == "Season"): + image_id = item.get("SeriesId") + season = item.get("IndexNumber") + type_info = "Season " + str(season).zfill(2) + elif(item_type == "Series"): + image_id = item.get("Id") + type_info = "Series" + elif(item_type == "Movie"): + image_id = item.get("Id") + type_info = "Movie" + elif(item_type == "Episode"): + image_id = item.get("SeriesId") + season = item.get("ParentIndexNumber") + eppNum = item.get("IndexNumber") + type_info = "S" + str(season).zfill(2) + "E" + str(eppNum).zfill(2) + + thumbPath = downloadUtils.imageUrl(image_id, "Primary", 0, 200, 200) + + listItem = xbmcgui.ListItem(label=item_name, label2=type_info, iconImage=thumbPath, thumbnailImage=thumbPath) + + actionUrl = "plugin://plugin.video.xbmb3c?id=" + item_id + "&mode=" + str(_MODE_ITEM_DETAILS) + listItem.setProperty("ActionUrl", actionUrl) + + otherItemsList.addItem(listItem) + + except Exception, e: + xbmc.log("Exception : " + str(e)) + pass + + + + # set the dialog data + self.getControl(3000).setLabel(name) + self.getControl(3001).setText(overview) + self.getControl(3009).setImage(image) + + def setPersonName(self, name): + self.personName = name + + def setInfo(self, data): + self.details = data + + def onFocus(self, controlId): + pass + + def doAction(self): + pass + + def closeDialog(self): + self.close() + + def onClick(self, controlID): + + if(controlID == 3002): + self.showMovies = True + + xbmc.executebuiltin('Dialog.Close(movieinformation)') + self.close() + + elif(controlID == 3010): + + #xbmc.executebuiltin("Dialog.Close(all,true)") + + itemList = self.getControl(3010) + item = itemList.getSelectedItem() + action = item.getProperty("ActionUrl") + + xbmc.executebuiltin("RunPlugin(" + action + ")") + + self.close() + + pass + diff --git a/resources/lib/RandomItems.py b/resources/lib/RandomItems.py new file mode 100644 index 0000000..20a6f17 --- /dev/null +++ b/resources/lib/RandomItems.py @@ -0,0 +1,319 @@ +################################################################################################# +# Random Info Updater +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +import urllib +from DownloadUtils import DownloadUtils + +_MODE_BASICPLAY=12 + +#define our global download utils +downloadUtils = DownloadUtils() + +class RandomInfoUpdaterThread(threading.Thread): + + logLevel = 0 + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C RandomInfoUpdaterThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C RandomInfoUpdaterThread -> " + msg) + + def run(self): + self.logMsg("Started") + + self.updateRandom() + lastRun = datetime.today() + + while (xbmc.abortRequested == False): + td = datetime.today() - lastRun + secTotal = td.seconds + + if(secTotal > 300): + self.updateRandom() + lastRun = datetime.today() + + xbmc.sleep(3000) + + self.logMsg("Exited") + + def updateRandom(self): + self.logMsg("updateRandomMovies Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + userid = downloadUtils.getUserId() + self.logMsg("updateRandomMovies UserID : " + userid) + + self.logMsg("Updating Random Movie List") + + randomUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=Random&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IncludeItemTypes=Movie&format=json" + + jsonData = downloadUtils.downloadUrl(randomUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Random Movie Json Data : " + str(result), level=2) + + result = result.get("Items") + if(result == None): + result = [] + + WINDOW = xbmcgui.Window( 10000 ) + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + rating = item.get("CommunityRating") + criticrating = item.get("CriticRating") + officialrating = item.get("OfficialRating") + criticratingsummary = "" + if(item.get("CriticRatingSummary") != None): + criticratingsummary = item.get("CriticRatingSummary").encode('utf-8') + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + year = item.get("ProductionYear") + if(item.get("RunTimeTicks") != None): + runtime = str(int(item.get("RunTimeTicks"))/(10000000*60)) + else: + runtime = "0" + + item_id = item.get("Id") + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + medium_fanart = downloadUtils.getArtwork(item, "Backdrop3") + if (item.get("ImageTags") != None and item.get("ImageTags").get("Thumb") != None): + realthumb = downloadUtils.getArtwork(item, "Thumb3") + else: + realthumb = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("RandomMovieMB3." + str(item_count) + ".Title = " + title, level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Plot = " + plot, level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Year = " + str(year), level=2) + self.logMsg("RandomMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2) + + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Title", title) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Art(fanart)", fanart) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Art(medium_fanart)", medium_fanart) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Art(clearlogo)", logo) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Art(poster)", thumbnail) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".RealThumb", realthumb) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Rating", str(rating)) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Mpaa", str(officialrating)) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".CriticRating", str(criticrating)) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Plot", plot) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Year", str(year)) + WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Runtime", str(runtime)) + + WINDOW.setProperty("RandomMovieMB3.Enabled", "true") + + item_count = item_count + 1 + + self.logMsg("Updating Random TV Show List") + + randomUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=10&Recursive=true&SortBy=Random&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=Episode&format=json" + + jsonData = downloadUtils.downloadUrl(randomUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Random TV Show Json Data : " + str(result), level=2) + + result = result.get("Items") + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + seriesName = "Missing Name" + if(item.get("SeriesName") != None): + seriesName = item.get("SeriesName").encode('utf-8') + + eppNumber = "X" + tempEpisodeNumber = "" + if(item.get("IndexNumber") != None): + eppNumber = item.get("IndexNumber") + if eppNumber < 10: + tempEpisodeNumber = "0" + str(eppNumber) + else: + tempEpisodeNumber = str(eppNumber) + + seasonNumber = item.get("ParentIndexNumber") + if seasonNumber < 10: + tempSeasonNumber = "0" + str(seasonNumber) + else: + tempSeasonNumber = str(seasonNumber) + rating = str(item.get("CommunityRating")) + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + + item_id = item.get("Id") + + poster = downloadUtils.getArtwork(item, "SeriesPrimary") + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + medium_fanart = downloadUtils.getArtwork(item, "Backdrop3") + banner = downloadUtils.getArtwork(item, "Banner") + if item.get("SeriesThumbImageTag") != None: + seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3") + else: + seriesthumbnail = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2) + self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2) + + + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".EpisodeTitle", title) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Rating", rating) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.medium_fanart)", medium_fanart) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster) + WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Plot", plot) + + WINDOW.setProperty("RandomEpisodeMB3.Enabled", "true") + + item_count = item_count + 1 + + # update random music + self.logMsg("Updating Random MusicList") + + randomUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=Random&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed,IsFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=MusicAlbum&format=json" + + jsonData = downloadUtils.downloadUrl(randomUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Random MusicList Json Data : " + str(result), level=2) + + result = result.get("Items") + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + artist = "Missing Artist" + if(item.get("AlbumArtist") != None): + artist = item.get("AlbumArtist").encode('utf-8') + + year = "0000" + if(item.get("ProductionYear") != None): + year = str(item.get("ProductionYear")) + plot = "Missing Plot" + if(item.get("Overview") != None): + plot = item.get("Overview").encode('utf-8') + + item_id = item.get("Id") + + if item.get("Type") == "MusicAlbum": + parentId = item.get("ParentLogoItemId") + + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + banner = downloadUtils.getArtwork(item, "Banner") + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Title = " + title, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Artist = " + artist, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Year = " + year, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Art(banner) = " + banner, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2) + self.logMsg("RandomAlbumMB3." + str(item_count) + ".Plot = " + plot, level=2) + + + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Title", title) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Artist", artist) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Year", year) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Rating", rating) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Art(fanart)", fanart) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Art(clearlogo)", logo) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Art(banner)", banner) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Art(poster)", thumbnail) + WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Plot", plot) + + WINDOW.setProperty("RandomAlbumMB3.Enabled", "true") + + item_count = item_count + 1 + + + \ No newline at end of file diff --git a/resources/lib/RecentItems.py b/resources/lib/RecentItems.py new file mode 100644 index 0000000..888e29c --- /dev/null +++ b/resources/lib/RecentItems.py @@ -0,0 +1,564 @@ +################################################################################################# +# Recent Info Updater +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +import urllib +from DownloadUtils import DownloadUtils + +_MODE_BASICPLAY=12 + +#define our global download utils +downloadUtils = DownloadUtils() + + +class RecentInfoUpdaterThread(threading.Thread): + + logLevel = 0 + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C RecentInfoUpdaterThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C RecentInfoUpdaterThread -> " + msg) + + def run(self): + self.logMsg("Started") + + self.updateRecent() + lastRun = datetime.today() + lastProfilePath = xbmc.translatePath('special://profile') + + while (xbmc.abortRequested == False): + td = datetime.today() - lastRun + secTotal = td.seconds + + profilePath = xbmc.translatePath('special://profile') + + updateInterval = 60 + if (xbmc.Player().isPlaying()): + updateInterval = 300 + + if(secTotal > updateInterval or lastProfilePath != profilePath): + self.updateRecent() + lastRun = datetime.today() + + lastProfilePath = profilePath + + xbmc.sleep(3000) + + self.logMsg("Exited") + + def updateRecent(self): + self.logMsg("updateRecent Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + userid = downloadUtils.getUserId() + + self.logMsg("UserName : " + userName + " UserID : " + userid) + + self.logMsg("Updating Recent Movie List") + + recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IncludeItemTypes=Movie&format=json" + + jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Recent Movie Json Data : " + str(result), level=2) + + result = result.get("Items") + if(result == None): + result = [] + + WINDOW = xbmcgui.Window( 10000 ) + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + rating = item.get("CommunityRating") + criticrating = item.get("CriticRating") + officialrating = item.get("OfficialRating") + criticratingsummary = "" + if(item.get("CriticRatingSummary") != None): + criticratingsummary = item.get("CriticRatingSummary").encode('utf-8') + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + year = item.get("ProductionYear") + if(item.get("RunTimeTicks") != None): + runtime = str(int(item.get("RunTimeTicks"))/(10000000*60)) + else: + runtime = "0" + + item_id = item.get("Id") + + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("LatestMovieMB3." + str(item_count) + ".Title = " + title, level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Plot = " + plot, level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Year = " + str(year), level=2) + self.logMsg("LatestMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2) + + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Title", title) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Art(fanart)", fanart) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Art(clearlogo)", logo) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Art(poster)", thumbnail) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Rating", str(rating)) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Mpaa", str(officialrating)) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".CriticRating", str(criticrating)) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Plot", plot) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Year", str(year)) + WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Runtime", str(runtime)) + + WINDOW.setProperty("LatestMovieMB3.Enabled", "true") + + item_count = item_count + 1 + + #Updating Recent Unplayed Movie List + self.logMsg("Updating Recent Unplayed Movie List") + + recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/Latest?Limit=30&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&IsPlayed=false&IncludeItemTypes=Movie&format=json" + + jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Recent Unplayed Movie Json Data : " + str(result), level=2) + + if(result == None): + result = [] + + WINDOW = xbmcgui.Window( 10000 ) + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + rating = item.get("CommunityRating") + criticrating = item.get("CriticRating") + officialrating = item.get("OfficialRating") + criticratingsummary = "" + if(item.get("CriticRatingSummary") != None): + criticratingsummary = item.get("CriticRatingSummary").encode('utf-8') + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + year = item.get("ProductionYear") + if(item.get("RunTimeTicks") != None): + runtime = str(int(item.get("RunTimeTicks"))/(10000000*60)) + else: + runtime = "0" + + item_id = item.get("Id") + + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + medium_fanart = downloadUtils.getArtwork(item, "Backdrop3") + + if (item.get("ImageTags") != None and item.get("ImageTags").get("Thumb") != None): + realthumb = downloadUtils.getArtwork(item, "Thumb3") + else: + realthumb = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Title = " + title, level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Plot = " + plot, level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Year = " + str(year), level=2) + self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2) + + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Title", title) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Art(fanart)", fanart) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Art(medium_fanart)", medium_fanart) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Art(clearlogo)", logo) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Art(poster)", thumbnail) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".RealThumb", realthumb) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Rating", str(rating)) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Mpaa", str(officialrating)) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".CriticRating", str(criticrating)) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Plot", plot) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Year", str(year)) + WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Runtime", str(runtime)) + + WINDOW.setProperty("LatestUnplayedMovieMB3.Enabled", "true") + + item_count = item_count + 1 + + #Updating Recent TV Show List + self.logMsg("Updating Recent TV Show List") + + recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=Episode&format=json" + + jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Recent TV Show Json Data : " + str(result), level=2) + + result = result.get("Items") + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + seriesName = "Missing Name" + if(item.get("SeriesName") != None): + seriesName = item.get("SeriesName").encode('utf-8') + + eppNumber = "X" + tempEpisodeNumber = "00" + if(item.get("IndexNumber") != None): + eppNumber = item.get("IndexNumber") + if eppNumber < 10: + tempEpisodeNumber = "0" + str(eppNumber) + else: + tempEpisodeNumber = str(eppNumber) + + seasonNumber = item.get("ParentIndexNumber") + if seasonNumber < 10: + tempSeasonNumber = "0" + str(seasonNumber) + else: + tempSeasonNumber = str(seasonNumber) + rating = str(item.get("CommunityRating")) + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + + item_id = item.get("Id") + + if item.get("Type") == "Episode" or item.get("Type") == "Season": + series_id = item.get("SeriesId") + + poster = downloadUtils.getArtwork(item, "SeriesPrimary") + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + banner = downloadUtils.getArtwork(item, "Banner") + if item.get("SeriesThumbImageTag") != None: + seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3") + else: + seriesthumbnail = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2) + self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2) + + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".EpisodeTitle", title) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Rating", rating) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster) + WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Plot", plot) + + WINDOW.setProperty("LatestEpisodeMB3.Enabled", "true") + + item_count = item_count + 1 + + #Updating Recent Unplayed TV Show List + self.logMsg("Updating Recent Unplayed TV Show List") + + recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/Latest?Limit=30&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview&IsPlayed=false&GroupItems=false&IncludeItemTypes=Episode&format=json" + + jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Recent Unplayed TV Show Json Data : " + str(result), level=2) + + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + seriesName = "Missing Name" + if(item.get("SeriesName") != None): + seriesName = item.get("SeriesName").encode('utf-8') + + eppNumber = "X" + tempEpisodeNumber = "00" + if(item.get("IndexNumber") != None): + eppNumber = item.get("IndexNumber") + if eppNumber < 10: + tempEpisodeNumber = "0" + str(eppNumber) + else: + tempEpisodeNumber = str(eppNumber) + + seasonNumber = item.get("ParentIndexNumber") + if seasonNumber < 10: + tempSeasonNumber = "0" + str(seasonNumber) + else: + tempSeasonNumber = str(seasonNumber) + rating = str(item.get("CommunityRating")) + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + + item_id = item.get("Id") + + if item.get("Type") == "Episode" or item.get("Type") == "Season": + series_id = item.get("SeriesId") + + poster = downloadUtils.getArtwork(item, "SeriesPrimary") + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + medium_fanart = downloadUtils.getArtwork(item, "Backdrop3") + banner = downloadUtils.getArtwork(item, "Banner") + if item.get("SeriesThumbImageTag") != None: + seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3") + else: + seriesthumbnail = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2) + self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2) + + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".EpisodeTitle", title) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Rating", rating) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.medium_fanart)", medium_fanart) + + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster) + WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Plot", plot) + + WINDOW.setProperty("LatestUnplayedEpisodeMB3.Enabled", "true") + + item_count = item_count + 1 + + #Updating Recent MusicList + self.logMsg("Updating Recent MusicList") + + recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=10&Recursive=true&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed,IsFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=MusicAlbum&format=json" + + jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Recent MusicList Json Data : " + str(result), level=2) + + result = result.get("Items") + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + artist = "Missing Artist" + if(item.get("AlbumArtist") != None): + artist = item.get("AlbumArtist").encode('utf-8') + + year = "0000" + if(item.get("ProductionYear") != None): + year = str(item.get("ProductionYear")) + plot = "Missing Plot" + if(item.get("Overview") != None): + plot = item.get("Overview").encode('utf-8') + + item_id = item.get("Id") + + if item.get("Type") == "MusicAlbum": + parentId = item.get("ParentLogoItemId") + + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + banner = downloadUtils.getArtwork(item, "Banner") + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Title = " + title, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Artist = " + artist, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Year = " + year, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Art(banner) = " + banner, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2) + self.logMsg("LatestAlbumMB3." + str(item_count) + ".Plot = " + plot, level=2) + + + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Title", title) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Artist", artist) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Year", year) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Rating", rating) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Art(fanart)", fanart) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Art(clearlogo)", logo) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Art(banner)", banner) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Art(poster)", thumbnail) + WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Plot", plot) + + WINDOW.setProperty("LatestAlbumMB3.Enabled", "true") + + item_count = item_count + 1 + + #Updating Recent Photo + self.logMsg("Updating Recent Photo") + + recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=10&Recursive=true&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=Photo&format=json" + + jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Recent Photo Json Data : " + str(result), level=2) + + result = result.get("Items") + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + + plot = "Missing Plot" + if(item.get("Overview") != None): + plot = item.get("Overview").encode('utf-8') + + item_id = item.get("Id") + + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + banner = downloadUtils.getArtwork(item, "Banner") + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("LatestPhotoMB3." + str(item_count) + ".Title = " + title, level=2) + self.logMsg("LatestPhotoMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2) + self.logMsg("LatestPhotoMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("LatestPhotoMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2) + self.logMsg("LatestPhotoMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2) + self.logMsg("LatestPhotoMB3." + str(item_count) + ".Art(banner) = " + banner, level=2) + self.logMsg("LatestPhotoMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2) + self.logMsg("LatestPhotoMB3." + str(item_count) + ".Plot = " + plot, level=2) + + + WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Title", title) + WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Thumb", thumbnail) + WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Art(fanart)", fanart) + WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Art(clearlogo)", logo) + WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Art(banner)", banner) + WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Art(poster)", thumbnail) + WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Plot", plot) + + WINDOW.setProperty("LatestPhotoMB3.Enabled", "true") + + item_count = item_count + 1 + \ No newline at end of file diff --git a/resources/lib/SearchDialog.py b/resources/lib/SearchDialog.py new file mode 100644 index 0000000..9aa69d8 --- /dev/null +++ b/resources/lib/SearchDialog.py @@ -0,0 +1,334 @@ +import sys +import xbmc +import xbmcgui +import xbmcaddon +import json as json +import urllib +from DownloadUtils import DownloadUtils +import threading + +_MODE_ITEM_DETAILS=17 + +class SearchDialog(xbmcgui.WindowXMLDialog): + + searchThread = None + + def __init__(self, *args, **kwargs): + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def onInit(self): + self.action_exitkeys_id = [10, 13] + + self.searchThread = BackgroundSearchThread() + self.searchThread.setDialog(self) + self.searchThread.start() + + + def onFocus(self, controlId): + pass + + def onAction(self, action): + #xbmc.log("onAction : " + str(action.getId()) + " " + str(action.getButtonCode()) + " " + str(action)) + + ACTION_PREVIOUS_MENU = 10 + ACTION_SELECT_ITEM = 7 + ACTION_PARENT_DIR = 9 + + if action == ACTION_PREVIOUS_MENU or action.getId() == 92: + searchTerm = self.getControl(3010).getLabel() + if(len(searchTerm) == 0): + self.close() + else: + searchTerm = searchTerm[:-1] + self.getControl(3010).setLabel(searchTerm) + self.searchThread.setSearch(searchTerm) + + #self.getControl(3010).setLabel(str(action.getButtonCode())) + + + def closeDialog(self): + thread.stopRunning() + self.close() + + def onClick(self, controlID): + + if(controlID == 3020): + self.addCharacter("A") + elif(controlID == 3021): + self.addCharacter("B") + elif(controlID == 3022): + self.addCharacter("C") + elif(controlID == 3023): + self.addCharacter("D") + elif(controlID == 3024): + self.addCharacter("E") + elif(controlID == 3025): + self.addCharacter("F") + elif(controlID == 3026): + self.addCharacter("G") + elif(controlID == 3027): + self.addCharacter("H") + elif(controlID == 3028): + self.addCharacter("I") + elif(controlID == 3029): + self.addCharacter("J") + elif(controlID == 3030): + self.addCharacter("K") + elif(controlID == 3031): + self.addCharacter("L") + elif(controlID == 3032): + self.addCharacter("M") + elif(controlID == 3033): + self.addCharacter("N") + elif(controlID == 3034): + self.addCharacter("O") + elif(controlID == 3035): + self.addCharacter("P") + elif(controlID == 3036): + self.addCharacter("Q") + elif(controlID == 3037): + self.addCharacter("R") + elif(controlID == 3038): + self.addCharacter("S") + elif(controlID == 3039): + self.addCharacter("T") + elif(controlID == 3040): + self.addCharacter("U") + elif(controlID == 3041): + self.addCharacter("V") + elif(controlID == 3042): + self.addCharacter("W") + elif(controlID == 3043): + self.addCharacter("X") + elif(controlID == 3044): + self.addCharacter("Y") + elif(controlID == 3045): + self.addCharacter("Z") + elif(controlID == 3046): + self.addCharacter("0") + elif(controlID == 3047): + self.addCharacter("1") + elif(controlID == 3048): + self.addCharacter("2") + elif(controlID == 3049): + self.addCharacter("3") + elif(controlID == 3050): + self.addCharacter("4") + elif(controlID == 3051): + self.addCharacter("5") + elif(controlID == 3052): + self.addCharacter("6") + elif(controlID == 3053): + self.addCharacter("7") + elif(controlID == 3054): + self.addCharacter("8") + elif(controlID == 3055): + self.addCharacter("9") + elif(controlID == 3056): + searchTerm = self.getControl(3010).getLabel() + searchTerm = searchTerm[:-1] + self.getControl(3010).setLabel(searchTerm) + self.searchThread.setSearch(searchTerm) + elif(controlID == 3057): + self.addCharacter(" ") + elif(controlID == 3058): + self.getControl(3010).setLabel("") + self.searchThread.setSearch("") + + elif(controlID == 3110): + + #xbmc.executebuiltin("Dialog.Close(all,true)") + itemList = self.getControl(3110) + item = itemList.getSelectedItem() + action = item.getProperty("ActionUrl") + xbmc.executebuiltin("RunPlugin(" + action + ")") + elif(controlID == 3111): + + #xbmc.executebuiltin("Dialog.Close(all,true)") + itemList = self.getControl(3111) + item = itemList.getSelectedItem() + action = item.getProperty("ActionUrl") + xbmc.executebuiltin("RunPlugin(" + action + ")") + elif(controlID == 3112): + + #xbmc.executebuiltin("Dialog.Close(all,true)") + itemList = self.getControl(3112) + item = itemList.getSelectedItem() + action = item.getProperty("ActionUrl") + xbmc.executebuiltin("RunPlugin(" + action + ")") + + pass + + def addCharacter(self, char): + searchTerm = self.getControl(3010).getLabel() + searchTerm = searchTerm + char + self.getControl(3010).setLabel(searchTerm) + self.searchThread.setSearch(searchTerm) + +class BackgroundSearchThread(threading.Thread): + + active = True + searchDialog = None + searchString = "" + + def __init__(self, *args): + xbmc.log("BackgroundSearchThread Init") + threading.Thread.__init__(self, *args) + + def setSearch(self, searchFor): + self.searchString = searchFor + + def stopRunning(self): + self.active = False + + def setDialog(self, searchDialog): + self.searchDialog = searchDialog + + def run(self): + xbmc.log("BackgroundSearchThread Started") + + lastSearchString = "" + + while(xbmc.abortRequested == False and self.active == True): + currentSearch = self.searchString + if(currentSearch != lastSearchString): + lastSearchString = currentSearch + self.doSearch(currentSearch) + + xbmc.sleep(2000) + + xbmc.log("BackgroundSearchThread Exited") + + def doSearch(self, searchTerm): + + movieResultsList = self.searchDialog.getControl(3110) + while(movieResultsList.size() > 0): + movieResultsList.removeItem(0) + #movieResultsList.reset() + + + seriesResultsList = self.searchDialog.getControl(3111) + while(seriesResultsList.size() > 0): + seriesResultsList.removeItem(0) + #seriesResultsList.reset() + + episodeResultsList = self.searchDialog.getControl(3112) + while(episodeResultsList.size() > 0): + episodeResultsList.removeItem(0) + #episodeResultsList.reset() + + if(len(searchTerm) == 0): + return + + __settings__ = xbmcaddon.Addon(id='plugin.video.xbmb3c') + port = __settings__.getSetting('port') + host = __settings__.getSetting('ipaddress') + server = host + ":" + port + + downloadUtils = DownloadUtils() + + # + # Process movies + # + search = urllib.quote(searchTerm) + url = "http://" + server + "/mediabrowser/Search/Hints?SearchTerm=" + search + "&Limit=10&IncludeItemTypes=Movie&format=json" + jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1) + result = json.loads(jsonData) + + items = result.get("SearchHints") + + if(items == None or len(items) == 0): + item = [] + + for item in items: + xbmc.log(str(item)) + + item_id = item.get("ItemId") + item_name = item.get("Name") + item_type = item.get("Type") + + typeLabel = "Movie" + + thumbPath = downloadUtils.imageUrl(item_id, "Primary", 0, 200, 200) + xbmc.log(thumbPath) + + listItem = xbmcgui.ListItem(label=item_name, label2=typeLabel, iconImage=thumbPath, thumbnailImage=thumbPath) + + actionUrl = "plugin://plugin.video.xbmb3c?id=" + item_id + "&mode=" + str(_MODE_ITEM_DETAILS) + listItem.setProperty("ActionUrl", actionUrl) + + movieResultsList.addItem(listItem) + + # + # Process series + # + search = urllib.quote(searchTerm) + url = "http://" + server + "/mediabrowser/Search/Hints?SearchTerm=" + search + "&Limit=10&IncludeItemTypes=Series&format=json" + jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1 ) + result = json.loads(jsonData) + + items = result.get("SearchHints") + + if(items == None or len(items) == 0): + item = [] + + for item in items: + xbmc.log(str(item)) + + item_id = item.get("ItemId") + item_name = item.get("Name") + item_type = item.get("Type") + + typeLabel = "" + image_id = "" + + image_id = item.get("ItemId") + typeLabel = "Series" + + thumbPath = downloadUtils.imageUrl(image_id, "Primary", 0, 200, 200) + xbmc.log(thumbPath) + + listItem = xbmcgui.ListItem(label=item_name, label2=typeLabel, iconImage=thumbPath, thumbnailImage=thumbPath) + + actionUrl = "plugin://plugin.video.xbmb3c?id=" + item_id + "&mode=" + str(_MODE_ITEM_DETAILS) + listItem.setProperty("ActionUrl", actionUrl) + + seriesResultsList.addItem(listItem) + + # + # Process episodes + # + search = urllib.quote(searchTerm) + url = "http://" + server + "/mediabrowser/Search/Hints?SearchTerm=" + search + "&Limit=10&IncludeItemTypes=Episode&format=json" + jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1 ) + result = json.loads(jsonData) + + items = result.get("SearchHints") + + if(items == None or len(items) == 0): + item = [] + + for item in items: + xbmc.log(str(item)) + + item_id = item.get("ItemId") + item_name = item.get("Name") + item_type = item.get("Type") + + image_id = item.get("ThumbImageItemId") + season = item.get("ParentIndexNumber") + eppNum = item.get("IndexNumber") + typeLabel = "S" + str(season).zfill(2) + "E" + str(eppNum).zfill(2) + + thumbPath = downloadUtils.imageUrl(image_id, "Primary", 0, 200, 200) + + xbmc.log(thumbPath) + + listItem = xbmcgui.ListItem(label=item_name, label2=typeLabel, iconImage=thumbPath, thumbnailImage=thumbPath) + + actionUrl = "plugin://plugin.video.xbmb3c?id=" + item_id + "&mode=" + str(_MODE_ITEM_DETAILS) + listItem.setProperty("ActionUrl", actionUrl) + + episodeResultsList.addItem(listItem) + + \ No newline at end of file diff --git a/resources/lib/SuggestedItems.py b/resources/lib/SuggestedItems.py new file mode 100644 index 0000000..d0feafe --- /dev/null +++ b/resources/lib/SuggestedItems.py @@ -0,0 +1,161 @@ +################################################################################################# +# Suggested Updater +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +import urllib +from DownloadUtils import DownloadUtils + +_MODE_BASICPLAY=12 + +#define our global download utils +downloadUtils = DownloadUtils() + +class SuggestedUpdaterThread(threading.Thread): + + logLevel = 0 + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C SuggestedUpdaterThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C SuggestedUpdaterThread -> " + msg) + + def run(self): + self.logMsg("Started") + + self.updateSuggested() + lastRun = datetime.today() + + while (xbmc.abortRequested == False): + td = datetime.today() - lastRun + secTotal = td.seconds + + if(secTotal > 300): + self.updateSuggested() + lastRun = datetime.today() + + xbmc.sleep(3000) + + self.logMsg("Exited") + + def updateSuggested(self): + self.logMsg("updateSuggested Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + userName = addonSettings.getSetting('username') + + userid = downloadUtils.getUserId() + self.logMsg("updateSuggested UserID : " + userid) + + self.logMsg("Updating Suggested List") + + suggestedUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Movies/Recommendations?UserId=" + userid + "&categoryLimit=1&ItemLimit=6&format=json" + jsonData = downloadUtils.downloadUrl(suggestedUrl, suppress=False, popup=1 ) + result = json.loads(jsonData) + self.logMsg("Suggested Movie Json Data : " + str(result), level=2) + basemovie = "Missing Base Title" + + if(result == None or len(result) == 0): + return + + if (result[0].get("BaselineItemName") != None): + basemovie = result[0].get("BaselineItemName").encode('utf-8') + + result = result[0].get("Items") + WINDOW = xbmcgui.Window( 10000 ) + if(result == None): + result = [] + + item_count = 1 + for item in result: + title = "Missing Title" + if(item.get("Name") != None): + title = item.get("Name").encode('utf-8') + + rating = item.get("CommunityRating") + criticrating = item.get("CriticRating") + officialrating = item.get("OfficialRating") + criticratingsummary = "" + if(item.get("CriticRatingSummary") != None): + criticratingsummary = item.get("CriticRatingSummary").encode('utf-8') + plot = item.get("Overview") + if plot == None: + plot='' + plot=plot.encode('utf-8') + year = item.get("ProductionYear") + if(item.get("RunTimeTicks") != None): + runtime = str(int(item.get("RunTimeTicks"))/(10000000*60)) + else: + runtime = "0" + + item_id = item.get("Id") + thumbnail = downloadUtils.getArtwork(item, "Primary") + logo = downloadUtils.getArtwork(item, "Logo") + fanart = downloadUtils.getArtwork(item, "Backdrop") + medium_fanart = downloadUtils.getArtwork(item, "Backdrop3") + if item.get("ImageTags").get("Thumb") != None: + realthumbnail = downloadUtils.getArtwork(item, "Thumb3") + else: + realthumbnail = fanart + + url = mb3Host + ":" + mb3Port + ',;' + item_id + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Title = " + title, level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Thumb = " + realthumbnail, level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Plot = " + plot, level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Year = " + str(year), level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2) + self.logMsg("SuggestedMovieMB3." + str(item_count) + ".SuggestedMovieTitle = " + basemovie, level=2) + + + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Title", title) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Thumb", realthumbnail) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Path", playUrl) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Art(fanart)", fanart) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Art(medium_fanart)", medium_fanart) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Art(clearlogo)", logo) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Art(poster)", thumbnail) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Rating", str(rating)) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Mpaa", str(officialrating)) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".CriticRating", str(criticrating)) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Plot", plot) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Year", str(year)) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Runtime", str(runtime)) + WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".SuggestedMovieTitle", basemovie) + + + WINDOW.setProperty("SuggestedMovieMB3.Enabled", "true") + + item_count = item_count + 1 + + diff --git a/resources/lib/ThemeMusic.py b/resources/lib/ThemeMusic.py new file mode 100644 index 0000000..620b3f6 --- /dev/null +++ b/resources/lib/ThemeMusic.py @@ -0,0 +1,183 @@ +################################################################################################# +# Start of ThemeMusic Thread +# plays theme music when applicable +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +import urllib +import urllib2 +import random + +from Utils import PlayUtils +from DownloadUtils import DownloadUtils + +#define our global download utils +downloadUtils = DownloadUtils() + +class ThemeMusicThread(threading.Thread): + + playingTheme = False + themeId = '' + volume = '' + themeMap = {} + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C ThemeMusicThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C ThemeMusicThread -> " + msg) + + def run(self): + self.logMsg("Started") + self.updateThemeMusic() + lastRun = datetime.today() + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + themeRefresh = 2 + + while (xbmc.abortRequested == False): + td = datetime.today() - lastRun + secTotal = td.seconds + + if (secTotal > themeRefresh): + self.updateThemeMusic() + lastRun = datetime.today() + + xbmc.sleep(2000) + + self.logMsg("Exited") + + + def updateThemeMusic(self): + self.logMsg("updateThemeMusic Called") + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + + newid = xbmc.getInfoLabel('ListItem.Property(ItemGUID)') + if newid != self.themeId: + if self.isPlayingZone() and self.playingTheme == True: + if xbmc.Player().isPlayingAudio(): + self.stop() + xbmc.sleep(1500) + id = xbmc.getInfoLabel('ListItem.Property(ItemGUID)') + if id != newid: + return + self.logMsg("updateThemeMusic itemGUID : " + id) + if self.isPlayingZone() and self.isChangeTheme(): + self.themeId = id + themeUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Items/" + id + "/ThemeSongs?format=json" + self.logMsg("updateThemeMusic themeUrl : " + themeUrl) + if themeUrl not in self.themeMap: + jsonData = downloadUtils.downloadUrl(themeUrl, suppress=False, popup=1 ) + theme = json.loads(jsonData) + + if(theme == None): + theme = [] + self.logMsg("updateThemeMusic added theme to map : " + themeUrl) + self.themeMap[themeUrl] = theme + elif themeUrl in self.themeMap: + theme = self.themeMap.get(themeUrl) + self.logMsg("updateThemeMusic retrieved theme from map : " + themeUrl) + + themeItems = theme.get("Items") + if themeItems != []: + themePlayUrl = PlayUtils().getPlayUrl(mb3Host + ":" + mb3Port,themeItems[0].get("Id"),themeItems[0]) + self.logMsg("updateThemeMusic themeMusicPath : " + str(themePlayUrl)) + self.playingTheme = True + self.setVolume(60) + xbmc.Player().play(themePlayUrl) + + elif themeItems == [] and self.playingTheme == True: + self.stop(True) + + if not self.isPlayingZone() and self.playingTheme == True: + # stop + if xbmc.Player().isPlayingAudio(): + self.stop() + + + def stop(self, forceStop = False): + # Only stop if playing audio + if xbmc.Player().isPlayingAudio() or forceStop == True: + self.playingTheme = False + cur_vol = self.getVolume() + + # Calculate how fast to fade the theme, this determines + # the number of step to drop the volume in + numSteps = 15 + vol_step = cur_vol / numSteps + # do not mute completely else the mute icon shows up + for step in range (0,(numSteps-1)): + vol = cur_vol - vol_step + self.setVolume(vol) + cur_vol = vol + xbmc.sleep(200) + xbmc.Player().stop() + self.setVolume(self.volume) + + # Works out if the currently displayed area on the screen is something + # that is deemed a zone where themes should be played + def isPlayingZone(self): + + if "plugin://plugin.video.xbmb3c" in xbmc.getInfoLabel( "ListItem.Path" ): + return True + + # Any other area is deemed to be a non play area + return False + + # Works out if we should change/start a theme + def isChangeTheme(self): + id = xbmc.getInfoLabel('ListItem.Property(ItemGUID)') + if id != "": + if self.volume == '': + self.volume = self.getVolume() + # we have something to start with + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + if addonSettings.getSetting('useThemeMusic') == "true": + # cool theme music is on continue + if id == self.themeId: + # same as before now do we need to restart + if addonSettings.getSetting('loopThemeMusic') == "true" and xbmc.Player().isPlayingAudio() == False: + return True + if id != self.themeId: + # new id return true + return True + + # still here return False + return False + + # This will return the volume in a range of 0-100 + def getVolume(self): + result = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": { "properties": [ "volume" ] }, "id": 1}') + + json_query = json.loads(result) + if "result" in json_query and json_query['result'].has_key('volume'): + # Get the volume value + volume = json_query['result']['volume'] + + return volume + + # Sets the volume in the range 0-100 + def setVolume(self, newvolume): + # Can't use the RPC version as that will display the volume dialog + # '{"jsonrpc": "2.0", "method": "Application.SetVolume", "params": { "volume": %d }, "id": 1}' + xbmc.executebuiltin('XBMC.SetVolume(%d)' % newvolume, True) + + \ No newline at end of file diff --git a/resources/lib/Utils.py b/resources/lib/Utils.py new file mode 100644 index 0000000..25bb6f8 --- /dev/null +++ b/resources/lib/Utils.py @@ -0,0 +1,167 @@ +################################################################################################# +# utils class +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +from datetime import datetime +from DownloadUtils import DownloadUtils +import urllib +import sys + +#define our global download utils +downloadUtils = DownloadUtils() + +########################################################################### +class PlayUtils(): + + def getPlayUrl(self, server, id, result): + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + # if the path is local and depending on the video quality play we can direct play it do so- + xbmc.log("XBMB3C getPlayUrl") + if self.isDirectPlay(result) == True: + xbmc.log("XBMB3C getPlayUrl -> Direct Play") + playurl = result.get("Path") + if playurl != None: + #We have a path to play so play it + USER_AGENT = 'QuickTime/7.7.4' + + # If the file it is not a media stub + if (result.get("IsPlaceHolder") != True): + if (result.get("VideoType") == "Dvd"): + playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO" + elif (result.get("VideoType") == "BluRay"): + playurl = playurl + "/BDMV/index.bdmv" + if addonSettings.getSetting('smbusername') == '': + playurl = playurl.replace("\\\\", "smb://") + else: + playurl = playurl.replace("\\\\", "smb://" + addonSettings.getSetting('smbusername') + ':' + addonSettings.getSetting('smbpassword') + '@') + playurl = playurl.replace("\\", "/") + + if ("apple.com" in playurl): + playurl += '?|User-Agent=%s' % USER_AGENT + if addonSettings.getSetting('playFromStream') == "true": + playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/stream?static=true' + mediaSources = result.get("MediaSources") + if(mediaSources != None): + if mediaSources[0].get('DefaultAudioStreamIndex') != None: + playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex')) + if mediaSources[0].get('DefaultSubtitleStreamIndex') != None: + playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultAudioStreamIndex')) + + + # elif self.isNetworkQualitySufficient(result) == True: + # xbmc.log("XBMB3C getPlayUrl -> Stream") + #No direct path but sufficient network so static stream + # if result.get("Type") == "Audio": + # playurl = 'http://' + server + '/mediabrowser/Audio/' + id + '/stream?static=true&mediaSourceId=' + id + #else: + # playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/stream?static=true&mediaSourceId=' + id + else: + #No path or has a path but not sufficient network so transcode + xbmc.log("XBMB3C getPlayUrl -> Transcode") + if result.get("Type") == "Audio": + playurl = 'http://' + server + '/mediabrowser/Audio/' + id + '/stream.mp3' + else: + txt_mac = downloadUtils.getMachineId() + playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/master.m3u8?mediaSourceId=' + id + playurl = playurl + '&videoCodec=h264' + playurl = playurl + '&AudioCodec=aac,ac3' + playurl = playurl + '&deviceId=' + txt_mac + playurl = playurl + '&VideoBitrate=' + str(int(self.getVideoBitRate()) * 1000) + mediaSources = result.get("MediaSources") + if(mediaSources != None): + if mediaSources[0].get('DefaultAudioStreamIndex') != None: + playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex')) + if mediaSources[0].get('DefaultSubtitleStreamIndex') != None: + playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultAudioStreamIndex')) + return playurl.encode('utf-8') + + # Works out if we are direct playing or not + def isDirectPlay(self, result): + if result.get("LocationType") == "FileSystem" and self.isNetworkQualitySufficient(result) == True and self.isLocalPath(result) == False: + return True + else: + return False + + + # Works out if the network quality can play directly or if transcoding is needed + def isNetworkQualitySufficient(self, result): + settingsVideoBitRate = self.getVideoBitRate() + settingsVideoBitRate = int(settingsVideoBitRate) * 1000 + mediaSources = result.get("MediaSources") + if(mediaSources != None): + if mediaSources[0].get('Bitrate') != None: + if settingsVideoBitRate < int(mediaSources[0].get('Bitrate')): + xbmc.log("XBMB3C isNetworkQualitySufficient -> FALSE bit rate - settingsVideoBitRate: " + str(settingsVideoBitRate) + " mediasource bitrate: " + str(mediaSources[0].get('Bitrate'))) + return False + else: + xbmc.log("XBMB3C isNetworkQualitySufficient -> TRUE bit rate") + return True + + # Any thing else is ok + xbmc.log("XBMB3C isNetworkQualitySufficient -> TRUE default") + return True + + + # get the addon video quality + def getVideoBitRate(self): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + videoQuality = addonSettings.getSetting('videoBitRate') + if (videoQuality == "0"): + return '664' + elif (videoQuality == "1"): + return '996' + elif (videoQuality == "2"): + return '1320' + elif (videoQuality == "3"): + return '2000' + elif (videoQuality == "4"): + return '3200' + elif (videoQuality == "5"): + return '4700' + elif (videoQuality == "6"): + return '6200' + elif (videoQuality == "7"): + return '7700' + elif (videoQuality == "8"): + return '9200' + elif (videoQuality == "9"): + return '10700' + elif (videoQuality == "10"): + return '12200' + elif (videoQuality == "11"): + return '13700' + elif (videoQuality == "12"): + return '15200' + elif (videoQuality == "13"): + return '16700' + elif (videoQuality == "14"): + return '18200' + elif (videoQuality == "15"): + return '20000' + elif (videoQuality == "16"): + return '40000' + elif (videoQuality == "17"): + return '100000' + elif (videoQuality == "18"): + return '1000000' + + # Works out if the network quality can play directly or if transcoding is needed + def isLocalPath(self, result): + playurl = result.get("Path") + if playurl != None: + #We have a path to play so play it + if ":\\" in playurl: + return True + else: + return False + + # default to not local + return False + diff --git a/resources/lib/WebSocketClient.py b/resources/lib/WebSocketClient.py new file mode 100644 index 0000000..7352d22 --- /dev/null +++ b/resources/lib/WebSocketClient.py @@ -0,0 +1,244 @@ +################################################################################################# +# WebSocket Client thread +################################################################################################# + +import xbmc +import xbmcgui +import xbmcaddon + +import json +import threading +import urllib +import socket +import websocket +from ClientInformation import ClientInformation + +_MODE_BASICPLAY=12 + +class WebSocketThread(threading.Thread): + + logLevel = 0 + client = None + keepRunning = True + + def __init__(self, *args): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + level = addonSettings.getSetting('logLevel') + self.logLevel = 0 + if(level != None): + self.logLevel = int(level) + + xbmc.log("XBMB3C WebSocketThread -> Log Level:" + str(self.logLevel)) + + threading.Thread.__init__(self, *args) + + def logMsg(self, msg, level = 1): + if(self.logLevel >= level): + xbmc.log("XBMB3C WebSocketThread -> " + msg) + + def playbackStarted(self, itemId): + if(self.client != None): + try: + self.logMsg("Sending Playback Started") + messageData = {} + messageData["MessageType"] = "PlaybackStart" + messageData["Data"] = itemId + "|true|audio,video" + messageString = json.dumps(messageData) + self.logMsg("Message Data : " + messageString) + self.client.send(messageString) + except Exception, e: + self.logMsg("Exception : " + str(e), level=0) + else: + self.logMsg("Sending Playback Started NO Object ERROR") + + def playbackStopped(self, itemId, ticks): + if(self.client != None): + try: + self.logMsg("Sending Playback Stopped") + messageData = {} + messageData["MessageType"] = "PlaybackStopped" + messageData["Data"] = itemId + "|" + str(ticks) + messageString = json.dumps(messageData) + self.client.send(messageString) + except Exception, e: + self.logMsg("Exception : " + str(e), level=0) + else: + self.logMsg("Sending Playback Stopped NO Object ERROR") + + def sendProgressUpdate(self, itemId, ticks): + if(self.client != None): + try: + self.logMsg("Sending Progress Update") + messageData = {} + messageData["MessageType"] = "PlaybackProgress" + messageData["Data"] = itemId + "|" + str(ticks) + "|false|false" + messageString = json.dumps(messageData) + self.logMsg("Message Data : " + messageString) + self.client.send(messageString) + except Exception, e: + self.logMsg("Exception : " + str(e), level=0) + else: + self.logMsg("Sending Progress Update NO Object ERROR") + + def stopClient(self): + # stopping the client is tricky, first set keep_running to false and then trigger one + # more message by requesting one SessionsStart message, this causes the + # client to receive the message and then exit + if(self.client != None): + self.logMsg("Stopping Client") + self.keepRunning = False + self.client.keep_running = False + self.logMsg("Stopping Client : KeepRunning set to False") + ''' + try: + self.keepRunning = False + self.client.keep_running = False + self.logMsg("Stopping Client") + self.logMsg("Calling Ping") + self.client.sock.ping() + + self.logMsg("Calling Socket Shutdown()") + self.client.sock.sock.shutdown(socket.SHUT_RDWR) + self.logMsg("Calling Socket Close()") + self.client.sock.sock.close() + self.logMsg("Stopping Client Done") + self.logMsg("Calling Ping") + self.client.sock.ping() + + except Exception, e: + self.logMsg("Exception : " + str(e), level=0) + ''' + else: + self.logMsg("Stopping Client NO Object ERROR") + + def on_message(self, ws, message): + self.logMsg("Message : " + str(message)) + result = json.loads(message) + + messageType = result.get("MessageType") + playCommand = result.get("PlayCommand") + data = result.get("Data") + + if(messageType != None and messageType == "Play" and data != None): + itemIds = data.get("ItemIds") + playCommand = data.get("PlayCommand") + if(playCommand != None and playCommand == "PlayNow"): + + startPositionTicks = data.get("StartPositionTicks") + self.logMsg("Playing Media With ID : " + itemIds[0]) + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + + url = mb3Host + ":" + mb3Port + ',;' + itemIds[0] + if(startPositionTicks == None): + url += ",;" + "-1" + else: + url += ",;" + str(startPositionTicks) + + playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY) + playUrl = playUrl.replace("\\\\","smb://") + playUrl = playUrl.replace("\\","/") + + xbmc.Player().play(playUrl) + + elif(messageType != None and messageType == "Playstate"): + command = data.get("Command") + if(command != None and command == "Stop"): + self.logMsg("Playback Stopped") + xbmc.executebuiltin('xbmc.activatewindow(10000)') + xbmc.Player().stop() + + if(command != None and command == "Seek"): + seekPositionTicks = data.get("SeekPositionTicks") + self.logMsg("Playback Seek : " + str(seekPositionTicks)) + seekTime = (seekPositionTicks / 1000) / 10000 + xbmc.Player().seekTime(seekTime) + + def on_error(self, ws, error): + self.logMsg("Error : " + str(error)) + + def on_close(self, ws): + self.logMsg("Closed") + + def on_open(self, ws): + try: + clientInfo = ClientInformation() + machineId = clientInfo.getMachineId() + version = clientInfo.getVersion() + messageData = {} + messageData["MessageType"] = "Identity" + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + deviceName = addonSettings.getSetting('deviceName') + deviceName = deviceName.replace("\"", "_") + + messageData["Data"] = "XBMC|" + machineId + "|" + version + "|" + deviceName + messageString = json.dumps(messageData) + self.logMsg("Opened : " + str(messageString)) + ws.send(messageString) + except Exception, e: + self.logMsg("Exception : " + str(e), level=0) + + def getWebSocketPort(self, host, port): + + userUrl = "http://" + host + ":" + port + "/mediabrowser/System/Info?format=json" + + try: + requesthandle = urllib.urlopen(userUrl, proxies={}) + jsonData = requesthandle.read() + requesthandle.close() + except Exception, e: + self.logMsg("WebSocketThread getWebSocketPort urlopen : " + str(e) + " (" + userUrl + ")", level=0) + return -1 + + try: + result = json.loads(jsonData) + except Exception, e: + self.logMsg("WebSocketThread getWebSocketPort jsonload : " + str(e) + " (" + jsonData + ")", level=2) + return -1 + + wsPort = result.get("WebSocketPortNumber") + if(wsPort != None): + return wsPort + else: + return -1 + + def run(self): + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + mb3Host = addonSettings.getSetting('ipaddress') + mb3Port = addonSettings.getSetting('port') + + if(self.logLevel >= 1): + websocket.enableTrace(True) + + wsPort = self.getWebSocketPort(mb3Host, mb3Port); + self.logMsg("WebSocketPortNumber = " + str(wsPort)) + if(wsPort == -1): + self.logMsg("Could not retrieve WebSocket port, can not run WebScoket Client") + return + + # Make a call to /System/Info. WebSocketPortNumber is the port hosting the web socket. + webSocketUrl = "ws://" + mb3Host + ":" + str(wsPort) + "/mediabrowser" + self.logMsg("WebSocket URL : " + webSocketUrl) + self.client = websocket.WebSocketApp(webSocketUrl, + on_message = self.on_message, + on_error = self.on_error, + on_close = self.on_close) + + self.client.on_open = self.on_open + + while(self.keepRunning): + self.logMsg("Client Starting") + self.client.run_forever() + if(self.keepRunning): + self.logMsg("Client Needs To Restart") + xbmc.sleep(10000) + + self.logMsg("Thread Exited") + + + + \ No newline at end of file diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py new file mode 100644 index 0000000..b93054b --- /dev/null +++ b/resources/lib/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/resources/lib/websocket.py b/resources/lib/websocket.py new file mode 100644 index 0000000..2dafc7d --- /dev/null +++ b/resources/lib/websocket.py @@ -0,0 +1,902 @@ +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" + + +import socket + +try: + import ssl + from ssl import SSLError + HAVE_SSL = True +except ImportError: + # dummy class of SSLError for ssl none-support environment. + class SSLError(Exception): + pass + + HAVE_SSL = False + +from urlparse import urlparse +import os +import array +import struct +import uuid +import hashlib +import base64 +import threading +import time +import logging +import traceback +import sys + +""" +websocket python client. +========================= + +This version support only hybi-13. +Please see http://tools.ietf.org/html/rfc6455 for protocol. +""" + + +# websocket supported version. +VERSION = 13 + +# closing frame status codes. +STATUS_NORMAL = 1000 +STATUS_GOING_AWAY = 1001 +STATUS_PROTOCOL_ERROR = 1002 +STATUS_UNSUPPORTED_DATA_TYPE = 1003 +STATUS_STATUS_NOT_AVAILABLE = 1005 +STATUS_ABNORMAL_CLOSED = 1006 +STATUS_INVALID_PAYLOAD = 1007 +STATUS_POLICY_VIOLATION = 1008 +STATUS_MESSAGE_TOO_BIG = 1009 +STATUS_INVALID_EXTENSION = 1010 +STATUS_UNEXPECTED_CONDITION = 1011 +STATUS_TLS_HANDSHAKE_ERROR = 1015 + +logger = logging.getLogger() + + +class WebSocketException(Exception): + """ + websocket exeception class. + """ + pass + + +class WebSocketConnectionClosedException(WebSocketException): + """ + If remote host closed the connection or some network error happened, + this exception will be raised. + """ + pass + +class WebSocketTimeoutException(WebSocketException): + """ + WebSocketTimeoutException will be raised at socket timeout during read/write data. + """ + pass + +default_timeout = None +traceEnabled = False + + +def enableTrace(tracable): + """ + turn on/off the tracability. + + tracable: boolean value. if set True, tracability is enabled. + """ + global traceEnabled + traceEnabled = tracable + if tracable: + if not logger.handlers: + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.DEBUG) + + +def setdefaulttimeout(timeout): + """ + Set the global timeout setting to connect. + + timeout: default socket timeout time. This value is second. + """ + global default_timeout + default_timeout = timeout + + +def getdefaulttimeout(): + """ + Return the global timeout setting(second) to connect. + """ + return default_timeout + + +def _parse_url(url): + """ + parse url and the result is tuple of + (hostname, port, resource path and the flag of secure mode) + + url: url string. + """ + if ":" not in url: + raise ValueError("url is invalid") + + scheme, url = url.split(":", 1) + + parsed = urlparse(url, scheme="http") + if parsed.hostname: + hostname = parsed.hostname + else: + raise ValueError("hostname is invalid") + port = 0 + if parsed.port: + port = parsed.port + + is_secure = False + if scheme == "ws": + if not port: + port = 80 + elif scheme == "wss": + is_secure = True + if not port: + port = 443 + else: + raise ValueError("scheme %s is invalid" % scheme) + + if parsed.path: + resource = parsed.path + else: + resource = "/" + + if parsed.query: + resource += "?" + parsed.query + + return (hostname, port, resource, is_secure) + + +def create_connection(url, timeout=None, **options): + """ + connect to url and return websocket object. + + Connect to url and return the WebSocket object. + Passing optional timeout parameter will set the timeout on the socket. + If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used. + You can customize using 'options'. + If you set "header" list object, you can set your own custom header. + + >>> conn = create_connection("ws://echo.websocket.org/", + ... header=["User-Agent: MyProgram", + ... "x-custom: header"]) + + + timeout: socket timeout time. This value is integer. + if you set None for this value, it means "use default_timeout value" + + options: current support option is only "header". + if you set header as dict value, the custom HTTP headers are added. + """ + sockopt = options.get("sockopt", []) + sslopt = options.get("sslopt", {}) + websock = WebSocket(sockopt=sockopt, sslopt=sslopt) + websock.settimeout(timeout if timeout is not None else default_timeout) + websock.connect(url, **options) + return websock + +_MAX_INTEGER = (1 << 32) -1 +_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) +_MAX_CHAR_BYTE = (1<<8) -1 + +# ref. Websocket gets an update, and it breaks stuff. +# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html + + +def _create_sec_websocket_key(): + uid = uuid.uuid4() + return base64.encodestring(uid.bytes).strip() + + +_HEADERS_TO_CHECK = { + "upgrade": "websocket", + "connection": "upgrade", + } + + +class ABNF(object): + """ + ABNF frame class. + see http://tools.ietf.org/html/rfc5234 + and http://tools.ietf.org/html/rfc6455#section-5.2 + """ + + # operation code values. + OPCODE_CONT = 0x0 + OPCODE_TEXT = 0x1 + OPCODE_BINARY = 0x2 + OPCODE_CLOSE = 0x8 + OPCODE_PING = 0x9 + OPCODE_PONG = 0xa + + # available operation code value tuple + OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, + OPCODE_PING, OPCODE_PONG) + + # opcode human readable string + OPCODE_MAP = { + OPCODE_CONT: "cont", + OPCODE_TEXT: "text", + OPCODE_BINARY: "binary", + OPCODE_CLOSE: "close", + OPCODE_PING: "ping", + OPCODE_PONG: "pong" + } + + # data length threashold. + LENGTH_7 = 0x7d + LENGTH_16 = 1 << 16 + LENGTH_63 = 1 << 63 + + def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0, + opcode=OPCODE_TEXT, mask=1, data=""): + """ + Constructor for ABNF. + please check RFC for arguments. + """ + self.fin = fin + self.rsv1 = rsv1 + self.rsv2 = rsv2 + self.rsv3 = rsv3 + self.opcode = opcode + self.mask = mask + self.data = data + self.get_mask_key = os.urandom + + def __str__(self): + return "fin=" + str(self.fin) \ + + " opcode=" + str(self.opcode) \ + + " data=" + str(self.data) + + @staticmethod + def create_frame(data, opcode): + """ + create frame to send text, binary and other data. + + data: data to send. This is string value(byte array). + if opcode is OPCODE_TEXT and this value is uniocde, + data value is conveted into unicode string, automatically. + + opcode: operation code. please see OPCODE_XXX. + """ + if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode): + data = data.encode("utf-8") + # mask must be set if send data from client + return ABNF(1, 0, 0, 0, opcode, 1, data) + + def format(self): + """ + format this object to string(byte array) to send data to server. + """ + if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]): + raise ValueError("not 0 or 1") + if self.opcode not in ABNF.OPCODES: + raise ValueError("Invalid OPCODE") + length = len(self.data) + if length >= ABNF.LENGTH_63: + raise ValueError("data is too long") + + frame_header = chr(self.fin << 7 + | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 + | self.opcode) + if length < ABNF.LENGTH_7: + frame_header += chr(self.mask << 7 | length) + elif length < ABNF.LENGTH_16: + frame_header += chr(self.mask << 7 | 0x7e) + frame_header += struct.pack("!H", length) + else: + frame_header += chr(self.mask << 7 | 0x7f) + frame_header += struct.pack("!Q", length) + + if not self.mask: + return frame_header + self.data + else: + mask_key = self.get_mask_key(4) + return frame_header + self._get_masked(mask_key) + + def _get_masked(self, mask_key): + s = ABNF.mask(mask_key, self.data) + return mask_key + "".join(s) + + @staticmethod + def mask(mask_key, data): + """ + mask or unmask data. Just do xor for each byte + + mask_key: 4 byte string(byte). + + data: data to mask/unmask. + """ + _m = array.array("B", mask_key) + _d = array.array("B", data) + for i in xrange(len(_d)): + _d[i] ^= _m[i % 4] + return _d.tostring() + + +class WebSocket(object): + """ + Low level WebSocket interface. + This class is based on + The WebSocket protocol draft-hixie-thewebsocketprotocol-76 + http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + + We can connect to the websocket server and send/recieve data. + The following example is a echo client. + + >>> import websocket + >>> ws = websocket.WebSocket() + >>> ws.connect("ws://echo.websocket.org") + >>> ws.send("Hello, Server") + >>> ws.recv() + 'Hello, Server' + >>> ws.close() + + get_mask_key: a callable to produce new mask keys, see the set_mask_key + function's docstring for more details + sockopt: values for socket.setsockopt. + sockopt must be tuple and each element is argument of sock.setscokopt. + sslopt: dict object for ssl socket option. + """ + + def __init__(self, get_mask_key=None, sockopt=None, sslopt=None): + """ + Initalize WebSocket object. + """ + if sockopt is None: + sockopt = [] + if sslopt is None: + sslopt = {} + self.connected = False + self.sock = socket.socket() + for opts in sockopt: + self.sock.setsockopt(*opts) + self.sslopt = sslopt + self.get_mask_key = get_mask_key + # Buffers over the packets from the layer beneath until desired amount + # bytes of bytes are received. + self._recv_buffer = [] + # These buffer over the build-up of a single frame. + self._frame_header = None + self._frame_length = None + self._frame_mask = None + self._cont_data = None + + def fileno(self): + return self.sock.fileno() + + def set_mask_key(self, func): + """ + set function to create musk key. You can custumize mask key generator. + Mainly, this is for testing purpose. + + func: callable object. the fuct must 1 argument as integer. + The argument means length of mask key. + This func must be return string(byte array), + which length is argument specified. + """ + self.get_mask_key = func + + def gettimeout(self): + """ + Get the websocket timeout(second). + """ + return self.sock.gettimeout() + + def settimeout(self, timeout): + """ + Set the timeout to the websocket. + + timeout: timeout time(second). + """ + self.sock.settimeout(timeout) + + timeout = property(gettimeout, settimeout) + + def connect(self, url, **options): + """ + Connect to url. url is websocket url scheme. ie. ws://host:port/resource + You can customize using 'options'. + If you set "header" dict object, you can set your own custom header. + + >>> ws = WebSocket() + >>> ws.connect("ws://echo.websocket.org/", + ... header={"User-Agent: MyProgram", + ... "x-custom: header"}) + + timeout: socket timeout time. This value is integer. + if you set None for this value, + it means "use default_timeout value" + + options: current support option is only "header". + if you set header as dict value, + the custom HTTP headers are added. + + """ + hostname, port, resource, is_secure = _parse_url(url) + # TODO: we need to support proxy + self.sock.connect((hostname, port)) + if is_secure: + if HAVE_SSL: + if self.sslopt is None: + sslopt = {} + else: + sslopt = self.sslopt + self.sock = ssl.wrap_socket(self.sock, **sslopt) + else: + raise WebSocketException("SSL not available.") + + self._handshake(hostname, port, resource, **options) + + def _handshake(self, host, port, resource, **options): + sock = self.sock + headers = [] + headers.append("GET %s HTTP/1.1" % resource) + headers.append("Upgrade: websocket") + headers.append("Connection: Upgrade") + if port == 80: + hostport = host + else: + hostport = "%s:%d" % (host, port) + headers.append("Host: %s" % hostport) + + if "origin" in options: + headers.append("Origin: %s" % options["origin"]) + else: + headers.append("Origin: http://%s" % hostport) + + key = _create_sec_websocket_key() + headers.append("Sec-WebSocket-Key: %s" % key) + headers.append("Sec-WebSocket-Version: %s" % VERSION) + if "header" in options: + headers.extend(options["header"]) + + headers.append("") + headers.append("") + + header_str = "\r\n".join(headers) + self._send(header_str) + if traceEnabled: + logger.debug("--- request header ---") + logger.debug(header_str) + logger.debug("-----------------------") + + status, resp_headers = self._read_headers() + if status != 101: + self.close() + raise WebSocketException("Handshake Status %d" % status) + + success = self._validate_header(resp_headers, key) + if not success: + self.close() + raise WebSocketException("Invalid WebSocket Header") + + self.connected = True + + def _validate_header(self, headers, key): + for k, v in _HEADERS_TO_CHECK.iteritems(): + r = headers.get(k, None) + if not r: + return False + r = r.lower() + if v != r: + return False + + result = headers.get("sec-websocket-accept", None) + if not result: + return False + result = result.lower() + + value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower() + return hashed == result + + def _read_headers(self): + status = None + headers = {} + if traceEnabled: + logger.debug("--- response header ---") + + while True: + line = self._recv_line() + if line == "\r\n": + break + line = line.strip() + if traceEnabled: + logger.debug(line) + if not status: + status_info = line.split(" ", 2) + status = int(status_info[1]) + else: + kv = line.split(":", 1) + if len(kv) == 2: + key, value = kv + headers[key.lower()] = value.strip().lower() + else: + raise WebSocketException("Invalid header") + + if traceEnabled: + logger.debug("-----------------------") + + return status, headers + + def send(self, payload, opcode=ABNF.OPCODE_TEXT): + """ + Send the data as string. + + payload: Payload must be utf-8 string or unicoce, + if the opcode is OPCODE_TEXT. + Otherwise, it must be string(byte array) + + opcode: operation code to send. Please see OPCODE_XXX. + """ + frame = ABNF.create_frame(payload, opcode) + if self.get_mask_key: + frame.get_mask_key = self.get_mask_key + data = frame.format() + length = len(data) + if traceEnabled: + logger.debug("send: " + repr(data)) + while data: + l = self._send(data) + data = data[l:] + return length + + def send_binary(self, payload): + return self.send(payload, ABNF.OPCODE_BINARY) + + def ping(self, payload=""): + """ + send ping data. + + payload: data payload to send server. + """ + self.send(payload, ABNF.OPCODE_PING) + + def pong(self, payload): + """ + send pong data. + + payload: data payload to send server. + """ + self.send(payload, ABNF.OPCODE_PONG) + + def recv(self): + """ + Receive string data(byte array) from the server. + + return value: string(byte array) value. + """ + opcode, data = self.recv_data() + return data + + def recv_data(self): + """ + Recieve data with operation code. + + return value: tuple of operation code and string(byte array) value. + """ + while True: + frame = self.recv_frame() + if not frame: + # handle error: + # 'NoneType' object has no attribute 'opcode' + raise WebSocketException("Not a valid frame %s" % frame) + elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT): + if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data: + raise WebSocketException("Illegal frame") + if self._cont_data: + self._cont_data[1] += frame.data + else: + self._cont_data = [frame.opcode, frame.data] + + if frame.fin: + data = self._cont_data + self._cont_data = None + return data + elif frame.opcode == ABNF.OPCODE_CLOSE: + self.send_close() + return (frame.opcode, None) + elif frame.opcode == ABNF.OPCODE_PING: + self.pong(frame.data) + + def recv_frame(self): + """ + recieve data as frame from server. + + return value: ABNF frame object. + """ + # Header + if self._frame_header is None: + self._frame_header = self._recv_strict(2) + b1 = ord(self._frame_header[0]) + fin = b1 >> 7 & 1 + rsv1 = b1 >> 6 & 1 + rsv2 = b1 >> 5 & 1 + rsv3 = b1 >> 4 & 1 + opcode = b1 & 0xf + b2 = ord(self._frame_header[1]) + has_mask = b2 >> 7 & 1 + # Frame length + if self._frame_length is None: + length_bits = b2 & 0x7f + if length_bits == 0x7e: + length_data = self._recv_strict(2) + self._frame_length = struct.unpack("!H", length_data)[0] + elif length_bits == 0x7f: + length_data = self._recv_strict(8) + self._frame_length = struct.unpack("!Q", length_data)[0] + else: + self._frame_length = length_bits + # Mask + if self._frame_mask is None: + self._frame_mask = self._recv_strict(4) if has_mask else "" + # Payload + payload = self._recv_strict(self._frame_length) + if has_mask: + payload = ABNF.mask(self._frame_mask, payload) + # Reset for next frame + self._frame_header = None + self._frame_length = None + self._frame_mask = None + return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload) + + + def send_close(self, status=STATUS_NORMAL, reason=""): + """ + send close data to the server. + + status: status code to send. see STATUS_XXX. + + reason: the reason to close. This must be string. + """ + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + + def close(self, status=STATUS_NORMAL, reason=""): + """ + Close Websocket object + + status: status code to send. see STATUS_XXX. + + reason: the reason to close. This must be string. + """ + if self.connected: + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") + + try: + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + timeout = self.sock.gettimeout() + self.sock.settimeout(3) + try: + frame = self.recv_frame() + if logger.isEnabledFor(logging.ERROR): + recv_status = struct.unpack("!H", frame.data)[0] + if recv_status != STATUS_NORMAL: + logger.error("close status: " + repr(recv_status)) + except: + pass + self.sock.settimeout(timeout) + self.sock.shutdown(socket.SHUT_RDWR) + except: + pass + self._closeInternal() + + def _closeInternal(self): + self.connected = False + self.sock.close() + + def _send(self, data): + try: + return self.sock.send(data) + except socket.timeout as e: + raise WebSocketTimeoutException(e.args[0]) + except Exception as e: + if "timed out" in e.args[0]: + raise WebSocketTimeoutException(e.args[0]) + else: + raise e + + def _recv(self, bufsize): + try: + bytes = self.sock.recv(bufsize) + except socket.timeout as e: + raise WebSocketTimeoutException(e.args[0]) + except SSLError as e: + if e.args[0] == "The read operation timed out": + raise WebSocketTimeoutException(e.args[0]) + else: + raise + if not bytes: + raise WebSocketConnectionClosedException() + return bytes + + + def _recv_strict(self, bufsize): + shortage = bufsize - sum(len(x) for x in self._recv_buffer) + while shortage > 0: + bytes = self._recv(shortage) + self._recv_buffer.append(bytes) + shortage -= len(bytes) + unified = "".join(self._recv_buffer) + if shortage == 0: + self._recv_buffer = [] + return unified + else: + self._recv_buffer = [unified[bufsize:]] + return unified[:bufsize] + + + def _recv_line(self): + line = [] + while True: + c = self._recv(1) + line.append(c) + if c == "\n": + break + return "".join(line) + + +class WebSocketApp(object): + """ + Higher level of APIs are provided. + The interface is like JavaScript WebSocket object. + """ + def __init__(self, url, header=[], + on_open=None, on_message=None, on_error=None, + on_close=None, keep_running=True, get_mask_key=None): + """ + url: websocket url. + header: custom header for websocket handshake. + on_open: callable object which is called at opening websocket. + this function has one argument. The arugment is this class object. + on_message: callbale object which is called when recieved data. + on_message has 2 arguments. + The 1st arugment is this class object. + The passing 2nd arugment is utf-8 string which we get from the server. + on_error: callable object which is called when we get error. + on_error has 2 arguments. + The 1st arugment is this class object. + The passing 2nd arugment is exception object. + on_close: callable object which is called when closed the connection. + this function has one argument. The arugment is this class object. + keep_running: a boolean flag indicating whether the app's main loop should + keep running, defaults to True + get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's + docstring for more information + """ + self.url = url + self.header = header + self.on_open = on_open + self.on_message = on_message + self.on_error = on_error + self.on_close = on_close + self.keep_running = keep_running + self.get_mask_key = get_mask_key + self.sock = None + + def send(self, data, opcode=ABNF.OPCODE_TEXT): + """ + send message. + data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode. + opcode: operation code of data. default is OPCODE_TEXT. + """ + if self.sock.send(data, opcode) == 0: + raise WebSocketConnectionClosedException() + + def close(self): + """ + close websocket connection. + """ + self.keep_running = False + self.sock.close() + + def _send_ping(self, interval): + while True: + for i in range(interval): + time.sleep(1) + if not self.keep_running: + return + self.sock.ping() + + def run_forever(self, sockopt=None, sslopt=None, ping_interval=0): + """ + run event loop for WebSocket framework. + This loop is infinite loop and is alive during websocket is available. + sockopt: values for socket.setsockopt. + sockopt must be tuple and each element is argument of sock.setscokopt. + sslopt: ssl socket optional dict. + ping_interval: automatically send "ping" command every specified period(second) + if set to 0, not send automatically. + """ + if sockopt is None: + sockopt = [] + if sslopt is None: + sslopt = {} + if self.sock: + raise WebSocketException("socket is already opened") + thread = None + + try: + self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt) + self.sock.settimeout(2)#default_timeout) + self.sock.connect(self.url, header=self.header) + self._callback(self.on_open) + + if ping_interval: + thread = threading.Thread(target=self._send_ping, args=(ping_interval,)) + thread.setDaemon(True) + thread.start() + + while self.keep_running: + + try: + data = self.sock.recv() + + if data is None or self.keep_running == False: + break + self._callback(self.on_message, data) + + except Exception, e: + #print str(e.args[0]) + if "timed out" not in e.args[0]: + raise e + + except Exception, e: + self._callback(self.on_error, e) + finally: + if thread: + self.keep_running = False + self.sock.close() + self._callback(self.on_close) + self.sock = None + + def _callback(self, callback, *args): + if callback: + try: + callback(self, *args) + except Exception, e: + logger.error(e) + if logger.isEnabledFor(logging.DEBUG): + _, _, tb = sys.exc_info() + traceback.print_tb(tb) + + +if __name__ == "__main__": + enableTrace(True) + ws = create_connection("ws://echo.websocket.org/") + print("Sending 'Hello, World'...") + ws.send("Hello, World") + print("Sent") + print("Receiving...") + result = ws.recv() + print("Received '%s'" % result) + ws.close() diff --git a/resources/mb3.png b/resources/mb3.png new file mode 100644 index 0000000..fc266e2 Binary files /dev/null and b/resources/mb3.png differ diff --git a/resources/media/BlankPoster.png b/resources/media/BlankPoster.png new file mode 100644 index 0000000..86c33dd Binary files /dev/null and b/resources/media/BlankPoster.png differ diff --git a/resources/settings.xml b/resources/settings.xml new file mode 100644 index 0000000..85dadd8 --- /dev/null +++ b/resources/settings.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/skins/default/720p/ItemInfo.xml b/resources/skins/default/720p/ItemInfo.xml new file mode 100644 index 0000000..64d100c --- /dev/null +++ b/resources/skins/default/720p/ItemInfo.xml @@ -0,0 +1,415 @@ + + + 3002 + 2 + + 1 + 120 + 50 + + dialogeffect + + + + 0 + 0 + 1040 + 600 + DialogBack.png + + + + 20 + 20 + 1000 + 560 + FF444444 + + + + 30 + 25 + 950 + 20 + left + + font24_title + FFFFFFFFFF + + + + 30 + 55 + 300 + 20 + left + + font18_title + FFFFFFFFFF + + + + + 40 + 130 + 250 + 140 + stretch + + + 40 + 265 + 250 + 5 + - + AAFFFFFF + stretch + + + + + 60 + 100 + 175 + 250 + stretch + + + 60 + 345 + 175 + 5 + - + AAFFFFFF + stretch + + + + 30 + 380 + 240 + 120 + 3002 + 3221 + 3235 + 3002 + 3221 + 200 + + + 60 + 0 + 60 + 20 + font10 + right + center + blue + selected + ListItem.Label + + + 65 + 0 + 180 + 20 + font10 + left + center + white + white + ListItem.Label2 + + + + + 0 + 0 + 240 + 20 + Control.HasFocus(3220) + MenuItemFO.png + VisibleFadeEffect + + + 60 + 0 + 60 + 20 + font10 + right + center + blue + selected + ListItem.Label + + + 65 + 0 + 180 + 20 + font10 + left + center + white + white + ListItem.Label2 + + + + + 270 + 380 + 20 + 120 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 3220 + 3226 + false + vertical + + + + 310 + 380 + 415 + 120 + 3221 + 3235 + 3235 + 3002 + - + 200 + + + 70 + 0 + 70 + 20 + font10 + right + center + blue + selected + ListItem.Label + + + 75 + 0 + 340 + 20 + font10 + left + center + white + white + ListItem.Label2 + + + + + 0 + 0 + 400 + 20 + Control.HasFocus(3226) + MenuItemFO.png + VisibleFadeEffect + + + 70 + 0 + 70 + 20 + font10 + right + center + blue + selected + ListItem.Label + + + 75 + 0 + 340 + 20 + font10 + left + center + white + white + ListItem.Label2 + + + + + + + 320 + 100 + 400 + 250 + font12 + + white + 3235 + true + + + 720 + 100 + 20 + 250 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 3226 + - + 3230 + false + vertical + + + + 760 + 100 + 245calc + 450 + 3235 + 3231 + - + - + 3231 + 200 + + + 0 + 0 + 60 + 60 + $INFO[Listitem.Icon] + scale + + + 65 + 0 + 160 + 30 + font12 + left + center + blue + selected + ListItem.Label + + + 65 + 30 + 160 + 30 + font10 + left + center + white + white + ListItem.Label2 + + + + + 0 + 0 + 60 + 60 + $INFO[Listitem.Icon] + scale + + + 60 + 0 + 160 + 30 + Control.HasFocus(3230) + MenuItemFO.png + VisibleFadeEffect + + + 65 + 0 + 160 + 30 + font12 + left + center + blue + selected + ListItem.Label + + + 65 + 30 + 160 + 30 + font10 + left + center + white + white + ListItem.Label2 + + + + + 985 + 100 + 20 + 450 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 3230 + - + false + vertical + + + + 30 + 520 + 150 + 40 + center + + font13 + - + 3220 + 3220 + + + + + \ No newline at end of file diff --git a/resources/skins/default/720p/PersonInfo.xml b/resources/skins/default/720p/PersonInfo.xml new file mode 100644 index 0000000..e05ca11 --- /dev/null +++ b/resources/skins/default/720p/PersonInfo.xml @@ -0,0 +1,205 @@ + + + 3010 + 2 + + 1 + 120 + 50 + + dialogeffect + + + 0 + 0 + 1040 + 600 + DialogBack.png + + + + 20 + 20 + 1000 + 560 + $INFO[Skin.CurrentTheme,special://skin/backgrounds/,.jpg] + ![Skin.HasSetting(UseCustomBackground) + !IsEmpty(Skin.String(CustomBackgroundPath))] + VisibleFadeEffect + FF444444 + + + + Dialog Header image + 40 + 16 + 960 + 40 + dialogheader.png + + + + header label + 40 + 20 + 960 + 30 + font13_title + + center + center + selected + black + + + + + + person name + 30 + 65 + 550 + 100 + left + + font13 + white + + + + 30 + 120 + 250 + 250 + keep + + + + text + 300 + 100 + 630 + 280 + left + + font12 + 3005 + + + 940 + 100 + 20 + 280 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 8 + 3001 + - + 3010 + false + vertical + + + + + 40 + 390 + 940 + 170 + - + - + 3005 + 3011 + 3011 + 200 + horizontal + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-nofocus.png + 5 + + + 0 + 150 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-nofocus.png + 5 + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-focus.png + 5 + Control.HasFocus(3010) + + + 0 + 150 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + + + 40 + 560 + 940 + 20 + ScrollBarH.png + ScrollBarH_bar.png + ScrollBarH_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 3010 + false + horizontal + + + + + diff --git a/resources/skins/default/720p/SearchDialog.xml b/resources/skins/default/720p/SearchDialog.xml new file mode 100644 index 0000000..c1ae3b3 --- /dev/null +++ b/resources/skins/default/720p/SearchDialog.xml @@ -0,0 +1,910 @@ + + + 3020 + 2 + + 1 + 120 + 50 + + + + + + 0 + 0 + 1040 + 600 + DialogBack.png + + + + 20 + 20 + 1000 + 560 + $INFO[Skin.CurrentTheme,special://skin/backgrounds/,.jpg] + ![Skin.HasSetting(UseCustomBackground) + !IsEmpty(Skin.String(CustomBackgroundPath))] + VisibleFadeEffect + FF444444 + + + + 25 + 98 + 190 + 30 + stretch + KeyboardEditArea.png + + + 30 + 100 + 180 + 40 + font13 + - + - + - + - + + + + 30 + 140 + + + + 0 + 0 + 30 + 30 + + - + 3021 + - + 3026 + center + center + font12 + + + + 30 + 0 + 30 + 30 + + 3020 + 3022 + - + 3027 + center + center + font12 + + + + 60 + 0 + 30 + 30 + + 3021 + 3023 + - + 3028 + center + center + font12 + + + + 90 + 0 + 30 + 30 + + 3022 + 3024 + - + 3029 + center + center + font12 + + + + 120 + 0 + 30 + 30 + + 3023 + 3025 + - + 3030 + center + center + font12 + + + + 150 + 0 + 30 + 30 + + 3024 + 3110 + - + 3031 + center + center + font12 + + + + + 0 + 30 + 30 + 30 + + - + 3027 + 3020 + 3032 + center + center + font12 + + + + 30 + 30 + 30 + 30 + + 3026 + 3028 + 3021 + 3033 + center + center + font12 + + + + 60 + 30 + 30 + 30 + + 3027 + 3029 + 3022 + 3034 + center + center + font12 + + + + 90 + 30 + 30 + 30 + + 3028 + 3030 + 3023 + 3035 + center + center + font12 + + + + 120 + 30 + 30 + 30 + + 3029 + 3031 + 3024 + 3036 + center + center + font12 + + + + 150 + 30 + 30 + 30 + + 3030 + 3110 + 3025 + 3037 + center + center + font12 + + + + + 0 + 60 + 30 + 30 + + - + 3033 + 3026 + 3038 + center + center + font12 + + + + 30 + 60 + 30 + 30 + + 3032 + 3034 + 3027 + 3039 + center + center + font12 + + + + 60 + 60 + 30 + 30 + + 3033 + 3035 + 3028 + 3040 + center + center + font12 + + + + 90 + 60 + 30 + 30 + + 3034 + 3036 + 3029 + 3041 + center + center + font12 + + + + 120 + 60 + 30 + 30 + + 3035 + 3037 + 3030 + 3042 + center + center + font12 + + + + 150 + 60 + 30 + 30 + + 3036 + 3110 + 3031 + 3043 + center + center + font12 + + + + + 0 + 90 + 30 + 30 + + - + 3039 + 3032 + 3044 + center + center + font12 + + + + 30 + 90 + 30 + 30 + + 3038 + 3040 + 3033 + 3045 + center + center + font12 + + + + 60 + 90 + 30 + 30 + + 3039 + 3041 + 3034 + 3046 + center + center + font12 + + + + 90 + 90 + 30 + 30 + + 3040 + 3042 + 3035 + 3047 + center + center + font12 + + + + 120 + 90 + 30 + 30 + + 3041 + 3043 + 3036 + 3048 + center + center + font12 + + + + 150 + 90 + 30 + 30 + + 3042 + 3110 + 3037 + 3049 + center + center + font12 + + + + + 0 + 120 + 30 + 30 + + - + 3045 + 3038 + 3050 + center + center + font12 + + + + 30 + 120 + 30 + 30 + + 3044 + 3046 + 3039 + 3051 + center + center + font12 + + + + 60 + 120 + 30 + 30 + + 3045 + 3047 + 3040 + 3052 + center + center + font12 + + + + 90 + 120 + 30 + 30 + + 3046 + 3048 + 3041 + 3053 + center + center + font12 + + + + 120 + 120 + 30 + 30 + + 3047 + 3049 + 3042 + 3054 + center + center + font12 + + + + 150 + 120 + 30 + 30 + + 3048 + 3110 + 3043 + 3055 + center + center + font12 + + + + + 0 + 150 + 30 + 30 + + - + 3051 + 3044 + 3056 + center + center + font12 + + + + 30 + 150 + 30 + 30 + + 3050 + 3052 + 3045 + 3056 + center + center + font12 + + + + 60 + 150 + 30 + 30 + + 3051 + 3053 + 3046 + 3057 + center + center + font12 + + + + 90 + 150 + 30 + 30 + + 3052 + 3054 + 3047 + 3057 + center + center + font12 + + + + 120 + 150 + 30 + 30 + + 3053 + 3055 + 3048 + 3058 + center + center + font12 + + + + 150 + 150 + 30 + 30 + + 3054 + 3110 + 3049 + 3058 + center + center + font12 + + + + + 0 + 180 + 60 + 30 + + - + 3057 + 3050 + - + center + center + font12 + + + + 60 + 180 + 60 + 30 + + 3056 + 3058 + 3052 + - + center + center + font12 + + + + 120 + 180 + 60 + 30 + + 3057 + 3110 + 3054 + - + center + center + font12 + + + + + + + + + 265 + 40 + 20 + 190 + + font14 + white + -90 + + + + 280 + 20 + 700 + 170 + 3025 + - + - + 3111 + - + 200 + horizontal + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-nofocus.png + 5 + + + 0 + 150 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-nofocus.png + 5 + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-focus.png + 5 + Control.HasFocus(3110) + + + 0 + 150 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + + + + + + 265 + 240 + 20 + 190 + + font14 + white + -90 + + + + 280 + 200 + 700 + 170 + 3025 + - + 3110 + 3112 + - + 200 + horizontal + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-nofocus.png + 5 + + + 0 + 150 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-nofocus.png + 5 + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-focus.png + 5 + Control.HasFocus(3111) + + + 0 + 150 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + + + + + + 265 + 420 + 20 + 190 + + font14 + white + -90 + + + + 280 + 380 + 700 + 190 + 3025 + - + 3111 + - + - + 200 + horizontal + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-nofocus.png + 5 + + + 0 + 150 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + 0 + 170 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-nofocus.png + 5 + + + 0 + 0 + 100 + 150 + $INFO[Listitem.Icon] + button-focus.png + 5 + Control.HasFocus(3112) + + + 0 + 150 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + 0 + 170 + 100 + 20 + left + font10 + FFFFFFFFFF + + + + + + + + diff --git a/service.py b/service.py new file mode 100644 index 0000000..0abec32 --- /dev/null +++ b/service.py @@ -0,0 +1,340 @@ +import xbmc +import xbmcgui +import xbmcaddon +import urllib +import httplib +import os +import time +import requests +import socket + +import threading +import json +from datetime import datetime +import xml.etree.ElementTree as xml + +import mimetypes +from threading import Thread +from urlparse import parse_qs +from urllib import urlretrieve + +from random import randint +import random +import urllib2 + +__cwd__ = xbmcaddon.Addon(id='plugin.video.xbmb3c').getAddonInfo('path') +__addon__ = xbmcaddon.Addon(id='plugin.video.xbmb3c') +__language__ = __addon__.getLocalizedString +BASE_RESOURCE_PATH = xbmc.translatePath( os.path.join( __cwd__, 'resources', 'lib' ) ) +sys.path.append(BASE_RESOURCE_PATH) +base_window = xbmcgui.Window( 10000 ) + +from InfoUpdater import InfoUpdaterThread +from NextUpItems import NextUpUpdaterThread +from SuggestedItems import SuggestedUpdaterThread +from RandomItems import RandomInfoUpdaterThread +from ArtworkLoader import ArtworkRotationThread +from ThemeMusic import ThemeMusicThread +from RecentItems import RecentInfoUpdaterThread +from InProgressItems import InProgressUpdaterThread +from WebSocketClient import WebSocketThread +from ClientInformation import ClientInformation +from MenuLoad import LoadMenuOptionsThread + +_MODE_BASICPLAY=12 + +def getAuthHeader(): + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + deviceName = addonSettings.getSetting('deviceName') + deviceName = deviceName.replace("\"", "_") # might need to url encode this as it is getting added to the header and is user entered data + clientInfo = ClientInformation() + txt_mac = clientInfo.getMachineId() + version = clientInfo.getVersion() + userid = xbmcgui.Window( 10000 ).getProperty("userid") + authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"XBMC\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" + headers = {'Accept-encoding': 'gzip', 'Authorization' : authString} + xbmc.log("XBMB3C Authentication Header : " + str(headers)) + return headers + +# start some worker threads + +newInProgressThread = None +if __addon__.getSetting('useInProgressUpdater') == "true": + newInProgressThread = InProgressUpdaterThread() + newInProgressThread.start() +else: + xbmc.log("XBMB3C Service InProgressUpdater Disabled") + +newRecentInfoThread = None +if __addon__.getSetting('useRecentInfoUpdater') == "true": + newRecentInfoThread = RecentInfoUpdaterThread() + newRecentInfoThread.start() +else: + xbmc.log("XBMB3C Service RecentInfoUpdater Disabled") + +newRandomInfoThread = None +if __addon__.getSetting('useRandomInfo') == "true": + newRandomInfoThread = RandomInfoUpdaterThread() + newRandomInfoThread.start() +else: + xbmc.log("XBMB3C Service RandomInfo Disabled") + +newNextUpThread = None +if __addon__.getSetting('useNextUp') == "true": + newNextUpThread = NextUpUpdaterThread() + newNextUpThread.start() +else: + xbmc.log("XBMB3C Service NextUp Disabled") + +newSuggestedThread = None +if __addon__.getSetting('useSuggested') == "true": + newSuggestedThread = SuggestedUpdaterThread() + newSuggestedThread.start() +else: + xbmc.log("XBMB3C Service Suggested Disabled") + +newWebSocketThread = None +if __addon__.getSetting('useWebSocketRemote') == "true": + newWebSocketThread = WebSocketThread() + newWebSocketThread.start() +else: + xbmc.log("XBMB3C Service WebSocketRemote Disabled") + +newMenuThread = None +if __addon__.getSetting('useMenuLoader') == "true": + newMenuThread = LoadMenuOptionsThread() + newMenuThread.start() +else: + xbmc.log("XBMB3C Service MenuLoader Disabled") + +artworkRotationThread = None +if __addon__.getSetting('useBackgroundLoader') == "true": + artworkRotationThread = ArtworkRotationThread() + artworkRotationThread.start() +else: + xbmc.log("XBMB3C Service BackgroundLoader Disabled") + +newThemeMusicThread = None +if __addon__.getSetting('useThemeMusic') == "true": + newThemeMusicThread = ThemeMusicThread() + newThemeMusicThread.start() +else: + xbmc.log("XBMB3C Service ThemeMusic Disabled") + +newInfoThread = None +if __addon__.getSetting('useInfoLoader') == "true": + newInfoThread = InfoUpdaterThread() + newInfoThread.start() +else: + xbmc.log("XBMB3C Service InfoLoader Disabled") + +def deleteItem (url): + return_value = xbmcgui.Dialog().yesno(__language__(30091),__language__(30092)) + if return_value: + xbmc.log('Deleting via URL: ' + url) + progress = xbmcgui.DialogProgress() + progress.create(__language__(30052), __language__(30053)) + resp = requests.delete(url, data='', headers=getAuthHeader()) + deleteSleep=0 + while deleteSleep<10: + xbmc.sleep(1000) + deleteSleep=deleteSleep+1 + progress.update(deleteSleep*10,__language__(30053)) + progress.close() + xbmc.executebuiltin("Container.Refresh") + return 1 + else: + return 0 + +def markWatched(url): + xbmc.log('XBMB3C Service -> Marking watched via: ' + url) + resp = requests.post(url, data='', headers=getAuthHeader()) + +def markUnWatched(url): + xbmc.log('XBMB3C Service -> Marking watched via: ' + url) + resp = requests.delete(url, data='', headers=getAuthHeader()) + +def setPosition (url, method): + xbmc.log('XBMB3C Service -> Setting position via: ' + url) + if method == 'POST': + resp = requests.post(url, data='', headers=getAuthHeader()) + elif method == 'DELETE': + resp = requests.delete(url, data='', headers=getAuthHeader()) + +def stopTranscoding(url): + xbmc.log('XBMB3C Service -> Stopping transcoding: ' + url) + resp = requests.delete(url, data='', headers=getAuthHeader()) + + +def hasData(data): + if(data == None or len(data) == 0 or data == "None"): + return False + else: + return True + +def stopAll(played_information): + + if(len(played_information) == 0): + return + + addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') + xbmc.log ("XBMB3C Service -> played_information : " + str(played_information)) + + for item_url in played_information: + data = played_information.get(item_url) + if(data != None): + xbmc.log ("XBMB3C Service -> item_url : " + item_url) + xbmc.log ("XBMB3C Service -> item_data : " + str(data)) + + watchedurl = data.get("watchedurl") + positionurl = data.get("positionurl") + deleteurl = data.get("deleteurl") + runtime = data.get("runtime") + currentPossition = data.get("currentPossition") + item_id = data.get("item_id") + + if(currentPossition != None and hasData(runtime) and hasData(positionurl) and hasData(watchedurl)): + runtimeTicks = int(runtime) + xbmc.log ("XBMB3C Service -> runtimeticks:" + str(runtimeTicks)) + percentComplete = (currentPossition * 10000000) / runtimeTicks + markPlayedAt = float(addonSettings.getSetting("markPlayedAt")) / 100 + + xbmc.log ("XBMB3C Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt)) + if (percentComplete > markPlayedAt): + + gotDeleted = 0 + if(deleteurl != None and deleteurl != ""): + xbmc.log ("XBMB3C Service -> Offering Delete:" + str(deleteurl)) + gotDeleted = deleteItem(deleteurl) + + if(gotDeleted == 0): + setPosition(positionurl + '/Progress?PositionTicks=0', 'POST') + if(newWebSocketThread != None): + newWebSocketThread.playbackStopped(item_id, str(0)) + markWatched(watchedurl) + else: + #markUnWatched(watchedurl) # this resets the LastPlayedDate and that causes issues with sortby PlayedDate so I removed it for now + if(newWebSocketThread != None): + newWebSocketThread.playbackStopped(item_id, str(int(currentPossition * 10000000))) + setPosition(positionurl + '?PositionTicks=' + str(int(currentPossition * 10000000)), 'DELETE') + + if(newNextUpThread != None): + newNextUpThread.updateNextUp() + + if(artworkRotationThread != None): + artworkRotationThread.updateActionUrls() + + played_information.clear() + + # stop transcoding - todo check we are actually transcoding? + clientInfo = ClientInformation() + txt_mac = clientInfo.getMachineId() + url = ("http://%s:%s/mediabrowser/Videos/ActiveEncodings" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port'))) + url = url + '?DeviceId=' + txt_mac + stopTranscoding(url) +class Service( xbmc.Player ): + + played_information = {} + + def __init__( self, *args ): + xbmc.log("XBMB3C Service -> starting monitor service") + self.played_information = {} + pass + + def onPlayBackStarted( self ): + # Will be called when xbmc starts playing a file + stopAll(self.played_information) + + currentFile = xbmc.Player().getPlayingFile() + xbmc.log("XBMB3C Service -> onPlayBackStarted" + currentFile) + + WINDOW = xbmcgui.Window( 10000 ) + watchedurl = WINDOW.getProperty(currentFile+"watchedurl") + deleteurl = WINDOW.getProperty(currentFile+"deleteurl") + positionurl = WINDOW.getProperty(currentFile+"positionurl") + runtime = WINDOW.getProperty(currentFile+"runtimeticks") + item_id = WINDOW.getProperty(currentFile+"item_id") + + # reset all these so they dont get used is xbmc plays a none + # xbmb3c MB item + # WINDOW.setProperty(currentFile+"watchedurl", "") + # WINDOW.setProperty(currentFile+"deleteurl", "") + # WINDOW.setProperty(currentFile+"positionurl", "") + # WINDOW.setProperty(currentFile+"runtimeticks", "") + # WINDOW.setProperty(currentFile+"item_id", "") + + if(item_id == None or len(item_id) == 0): + return + + if(newWebSocketThread != None): + newWebSocketThread.playbackStarted(item_id) + + if (watchedurl != "" and positionurl != ""): + + data = {} + data["watchedurl"] = watchedurl + data["deleteurl"] = deleteurl + data["positionurl"] = positionurl + data["runtime"] = runtime + data["item_id"] = item_id + self.played_information[currentFile] = data + + xbmc.log("XBMB3C Service -> ADDING_FILE : " + currentFile) + xbmc.log("XBMB3C Service -> ADDING_FILE : " + str(self.played_information)) + + # reset in progress possition + setPosition(positionurl + '/Progress?PositionTicks=0', 'POST') + + def onPlayBackEnded( self ): + # Will be called when xbmc stops playing a file + xbmc.log("XBMB3C Service -> onPlayBackEnded") + stopAll(self.played_information) + + def onPlayBackStopped( self ): + # Will be called when user stops xbmc playing a file + xbmc.log("XBMB3C Service -> onPlayBackStopped") + stopAll(self.played_information) + +monitor = Service() +lastProgressUpdate = datetime.today() + +addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') +if socket.gethostname() != None and socket.gethostname() != '' and addonSettings.getSetting("deviceName") == 'XBMB3C': + addonSettings.setSetting("deviceName", socket.gethostname()) + +while not xbmc.abortRequested: + if xbmc.Player().isPlaying(): + try: + + playTime = xbmc.Player().getTime() + currentFile = xbmc.Player().getPlayingFile() + + if(monitor.played_information.get(currentFile) != None): + monitor.played_information[currentFile]["currentPossition"] = playTime + + # send update + td = datetime.today() - lastProgressUpdate + secDiff = td.seconds + if(secDiff > 10): + if(monitor.played_information.get(currentFile) != None and monitor.played_information.get(currentFile).get("item_id") != None): + item_id = monitor.played_information.get(currentFile).get("item_id") + if(newWebSocketThread != None): + newWebSocketThread.sendProgressUpdate(item_id, str(int(playTime * 10000000))) + lastProgressUpdate = datetime.today() + + except Exception, e: + xbmc.log("XBMB3C Service -> Exception in Playback Monitor : " + str(e)) + pass + + xbmc.sleep(1000) + xbmcgui.Window(10000).setProperty("XBMB3C_Service_Timestamp", str(int(time.time()))) + +# stop the WebSocket client +if(newWebSocketThread != None): + newWebSocketThread.stopClient() + +# stop the image proxy +keepServing = False + +xbmc.log("XBMB3C Service -> Service shutting down") +