diff options
author | Shaun <shaun@bluebit.com.au> | 2017-07-16 00:09:51 +1000 |
---|---|---|
committer | enen92 <enen92@users.noreply.github.com> | 2017-07-15 15:09:51 +0100 |
commit | 49d6dd033fc083c4ee6f69947861c0d923144041 (patch) | |
tree | 541668912f24621431778016b2ad3ccb4ad08698 /plugin.video.embycon | |
parent | 09c5e3fe8977c5ffaf6599b3c6a35405a8a141ca (diff) |
[plugin.video.embycon] 1.3.17 (#1309)
[plugin.video.embycon] 1.3.17
Diffstat (limited to 'plugin.video.embycon')
30 files changed, 6773 insertions, 0 deletions
diff --git a/plugin.video.embycon/LICENSE.txt b/plugin.video.embycon/LICENSE.txt new file mode 100644 index 0000000..1c9b0bd --- /dev/null +++ b/plugin.video.embycon/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/plugin.video.embycon/addon.xml b/plugin.video.embycon/addon.xml new file mode 100644 index 0000000..8bf3c9b --- /dev/null +++ b/plugin.video.embycon/addon.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<addon id="plugin.video.embycon" + name="EmbyCon" + version="1.3.17" + provider-name="Team B"> + <requires> + <import addon="xbmc.python" version="2.25.0"/> + </requires> + <extension point="xbmc.python.pluginsource" library="default.py"> + <provides>video</provides> + </extension> + <extension point="xbmc.service" library="service.py" start="login"> + </extension> + <extension point="xbmc.addon.metadata"> + <platform>all</platform> + <language>en</language> + <license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license> + <forum>https://emby.media/community/index.php?/topic/46651-embycon/</forum> + <website>https://emby.media/community/index.php?/topic/46651-embycon/</website> + <source>https://github.com/faush01/plugin.video.embycon</source> + <summary lang="en_GB">View and play your Emby media library.</summary> + <description lang="en_GB">An addon to allow you to view and playback your Emby (www.emby.media) Movie and TV Show collection.</description> + </extension> +</addon> diff --git a/plugin.video.embycon/default.py b/plugin.video.embycon/default.py new file mode 100644 index 0000000..26170c2 --- /dev/null +++ b/plugin.video.embycon/default.py @@ -0,0 +1,13 @@ +# Gnu General Public License - see LICENSE.TXT + +from resources.lib.simple_logging import SimpleLogging +from resources.lib.functions import mainEntryPoint + +log = SimpleLogging('default') + +log.debug("About to enter mainEntryPoint()") + +mainEntryPoint() + +# clear done and exit. +# sys.modules.clear() diff --git a/plugin.video.embycon/fanart.jpg b/plugin.video.embycon/fanart.jpg Binary files differnew file mode 100644 index 0000000..4c705f6 --- /dev/null +++ b/plugin.video.embycon/fanart.jpg diff --git a/plugin.video.embycon/icon.png b/plugin.video.embycon/icon.png Binary files differnew file mode 100644 index 0000000..f399123 --- /dev/null +++ b/plugin.video.embycon/icon.png diff --git a/plugin.video.embycon/resources/__init__.py b/plugin.video.embycon/resources/__init__.py new file mode 100644 index 0000000..b93054b --- /dev/null +++ b/plugin.video.embycon/resources/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/plugin.video.embycon/resources/language/resource.language.en_gb/strings.po b/plugin.video.embycon/resources/language/resource.language.en_gb/strings.po new file mode 100644 index 0000000..de98dc2 --- /dev/null +++ b/plugin.video.embycon/resources/language/resource.language.en_gb/strings.po @@ -0,0 +1,544 @@ +msgid "" +msgstr "" +"Project-Id-Version: EmbyCon\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgctxt "#30000" +msgid "Server Address: " +msgstr "" + +msgctxt "#30001" +msgid "Server Port: " +msgstr "" + +msgctxt "#30002" +msgid "Use HTTPS: " +msgstr "" + +msgctxt "#30003" +msgid "Verify HTTPS Certificate: " +msgstr "" + +msgctxt "#30005" +msgid "Username: " +msgstr "" + +msgctxt "#30006" +msgid "Password: " +msgstr "" + +msgctxt "#30007" +msgid "Samba Username: " +msgstr "" + +msgctxt "#30008" +msgid "Samba Password: " +msgstr "" + +msgctxt "#30010" +msgid "Enable performance profiling" +msgstr "" + +msgctxt "#30011" +msgid "Detect Server" +msgstr "" + +msgctxt "#30012" +msgid "Change User" +msgstr "" + +msgctxt "#30014" +msgid "Emby" +msgstr "" + +msgctxt "#30015" +msgid "Network" +msgstr "" + +msgctxt "#30016" +msgid "Device Name" +msgstr "" + +msgctxt "#30017" +msgid "Show Connected Clients" +msgstr "" + +msgctxt "#30018" +msgid "Number of items to show in filtered lists" +msgstr "" + +msgctxt "#30019" +msgid "Filtered episode name format" +msgstr "" + +msgctxt "#30020" +msgid "Flatten single season" +msgstr "" + +msgctxt "#30022" +msgid "Advanced" +msgstr "" + +msgctxt "#30024" +msgid "Username:" +msgstr "" + +msgctxt "#30025" +msgid "Password:" +msgstr "" + +msgctxt "#30044" +msgid "Incorrect Username/Password" +msgstr "" + +msgctxt "#30045" +msgid "Username not found" +msgstr "" + +msgctxt "#30052" +msgid "Deleting" +msgstr "" + +msgctxt "#30053" +msgid "Waiting for server to delete" +msgstr "" + +msgctxt "#30060" +msgid "%s (Secured)" +msgstr "" + +msgctxt "#30061" +msgid "%s (User-defined)" +msgstr "" + +msgctxt "#30062" +msgid "Hidden user (User-input)" +msgstr "" + +msgctxt "#30063" +msgid "N/A" +msgstr "" + +msgctxt "#30091" +msgid "Confirm file delete?" +msgstr "" + +msgctxt "#30092" +msgid "Delete this item? This action will delete media and associated data files." +msgstr "" + +msgctxt "#30110" +msgid "Interface" +msgstr "" + +msgctxt "#30112" +msgid "Loading Content" +msgstr "" + +msgctxt "#30113" +msgid "Retrieving Data" +msgstr "" + +msgctxt "#30114" +msgid "On Resume Jump Back Seconds" +msgstr "" + +msgctxt "#30116" +msgid "Add Item and Played Counts" +msgstr "" + +msgctxt "#30117" +msgid "Background Art Refresh Rate (seconds)" +msgstr "" + +msgctxt "#30118" +msgid "Add Resume Percent" +msgstr "" + +msgctxt "#30119" +msgid "Add Episode Number" +msgstr "" + +msgctxt "#30120" +msgid "Show Load Progress" +msgstr "" + +msgctxt "#30121" +msgid "On Resume" +msgstr "" + +msgctxt "#30125" +msgid "Done" +msgstr "" + +msgctxt "#30126" +msgid "Processing Item : " +msgstr "" + +msgctxt "#30128" +msgid "Play Error" +msgstr "" + +msgctxt "#30129" +msgid "This item is not playable" +msgstr "" + +msgctxt "#30135" +msgid "Error" +msgstr "" + +msgctxt "#30136" +msgid "EmbyCon service is not running" +msgstr "" + +msgctxt "#30137" +msgid "Please restart Kodi" +msgstr "" + +msgctxt "#30138" +msgid "Cache Emby Data Locally" +msgstr "" + +msgctxt "#30139" +msgid "No Media Type Set" +msgstr "" + +msgctxt "#30150" +msgid "Skin does not support setting views" +msgstr "" + +msgctxt "#30151" +msgid "Select item action (Requires Restart)" +msgstr "" + +msgctxt "#30162" +msgid "Add Season Number" +msgstr "" + +msgctxt "#30163" +msgid "Add (cc) if subtitle is available" +msgstr "" + +msgctxt "#30166" +msgid "Select Server" +msgstr "" + +msgctxt "#30167" +msgid "Server Detection Succeeded" +msgstr "" + +msgctxt "#30168" +msgid "Found server" +msgstr "" + +msgctxt "#30169" +msgid "Address: " +msgstr "" + +msgctxt "#30180" +msgid "Select User" +msgstr "" + +msgctxt "#30181" +msgid "Include Overview" +msgstr "" + +msgctxt "#30182" +msgid "Include Media Info" +msgstr "" + +msgctxt "#30183" +msgid "Include People" +msgstr "" + +msgctxt "#30200" +msgid "URL error: %s" +msgstr "" + +msgctxt "#30201" +msgid "Unable to connect to server" +msgstr "" + +msgctxt "#30202" +msgid "Error Extracting Server host:port" +msgstr "" + +msgctxt "#30203" +msgid "Error in ArtworkRotationThread" +msgstr "" + +msgctxt "#30204" +msgid "Unable to connect to host" +msgstr "" + +msgctxt "#30205" +msgid "Error in LoadMenuOptionsThread" +msgstr "" + +msgctxt "#30206" +msgid "Playback Type" +msgstr "" + +msgctxt "#30207" +msgid "Playback" +msgstr "" + +msgctxt "#30208" +msgid "Transcode Bitrate (Kbits)" +msgstr "" + +msgctxt "#30209" +msgid "File Direct Path" +msgstr "" + +msgctxt "#30210" +msgid "HTTP Direct Stream" +msgstr "" + +msgctxt "#30211" +msgid "HTTP Transcode" +msgstr "" + +msgctxt "#30212" +msgid "Transcode Max Width" +msgstr "" + +msgctxt "#30213" +msgid "Transcode Force 8 Bit" +msgstr "" + +msgctxt "#30214" +msgid "Events" +msgstr "" + +msgctxt "#30215" +msgid "On Playback Stop (100% = disabled)" +msgstr "" + +msgctxt "#30217" +msgid "Prompt to delete episode after %" +msgstr "" + +msgctxt "#30218" +msgid "Prompt to play next episode after %" +msgstr "" + +msgctxt "#30220" +msgid "Prompt to delete movie after %" +msgstr "" + +msgctxt "#30221" +msgid "On Playback Stop" +msgstr "" + +msgctxt "#30229" +msgid "TV Shows" +msgstr "" + +msgctxt "#30230" +msgid "Default View" +msgstr "" + +msgctxt "#30231" +msgid "Movies" +msgstr "" + +msgctxt "#30232" +msgid "BoxSets" +msgstr "" + +msgctxt "#30233" +msgid "Series" +msgstr "" + +msgctxt "#30234" +msgid "Seasons" +msgstr "" + +msgctxt "#30235" +msgid "Episodes" +msgstr "" + +msgctxt "#30236" +msgid "Save" +msgstr "" + +msgctxt "#30237" +msgid "Start from beginning" +msgstr "" + +msgctxt "#30238" +msgid "Default Sort" +msgstr "" + +msgctxt "#30245" +msgid "Next page" +msgstr "" + +msgctxt "#30246" +msgid "Search" +msgstr "" + +msgctxt "#30247" +msgid "Widgets" +msgstr "" + +msgctxt "#30248" +msgid "Emby - Movies" +msgstr "" + +msgctxt "#30249" +msgid "Emby - TV Shows" +msgstr "" + +msgctxt "#30250" +msgid "Unknown" +msgstr "" + +msgctxt "#30251" +msgid "Movies (Genre)" +msgstr "" + +msgctxt "#30252" +msgid "Movies (A-Z)" +msgstr "" + +msgctxt "#30253" +msgid "Change User" +msgstr "" + +msgctxt "#30254" +msgid "Show Settings" +msgstr "" + +msgctxt "#30256" +msgid "Movies - All" +msgstr "" + +msgctxt "#30257" +msgid "Movies - Recently Added" +msgstr "" + +msgctxt "#30258" +msgid "Movies - In Progress" +msgstr "" + +msgctxt "#30259" +msgid "Movies - Favorites" +msgstr "" + +msgctxt "#30260" +msgid "BoxSets" +msgstr "" + +msgctxt "#30261" +msgid "TV Shows - All" +msgstr "" + +msgctxt "#30262" +msgid "TV Shows - Favorites" +msgstr "" + +msgctxt "#30263" +msgid "Episodes - Recently Added" +msgstr "" + +msgctxt "#30264" +msgid "Episodes - In Progress" +msgstr "" + +msgctxt "#30265" +msgid "Episodes - Next Up" +msgstr "" + +msgctxt "#30266" +msgid "Upcoming TV" +msgstr "" + +msgctxt "#30267" +msgid " - In Progress" +msgstr "" + +msgctxt "#30268" +msgid " - Recently Added" +msgstr "" + +msgctxt "#30269" +msgid "Movies - Random" +msgstr "" + +msgctxt "#30270" +msgid "Emby: Mark watched" +msgstr "" + +msgctxt "#30271" +msgid "Emby: Mark unwatched" +msgstr "" + +msgctxt "#30272" +msgid "Emby: Set favourite" +msgstr "" + +msgctxt "#30273" +msgid "Emby: Unset favourite" +msgstr "" + +msgctxt "#30274" +msgid "Emby: Delete" +msgstr "" + +msgctxt "#30275" +msgid "Emby: Force Transcode" +msgstr "" + +msgctxt "#30276" +msgid "Extra Resume Prompt Detected" +msgstr "" + +msgctxt "#30277" +msgid "EmbyCon needs to prompt for resume on partily played items, Kodi can also prompt, this can cause a double prompt. Do you want to remove the double prompt?" +msgstr "" + +msgctxt "#30278" +msgid " - Next Up" +msgstr "" + +msgctxt "#30279" +msgid "TV Shows - Unwatched" +msgstr "" + +msgctxt "#30280" +msgid "Missing Title" +msgstr "" + +msgctxt "#30281" +msgid "Your skin %s is currently not supported" +msgstr "" + +msgctxt "#30282" +msgid "No servers detected" +msgstr "" + +msgctxt "#30283" +msgid "Play Next Episode" +msgstr "" + +msgctxt "#30284" +msgid "Do you want to play the next episode?" +msgstr "" + +msgctxt "#30285" +msgid " - Unwatched" +msgstr "" + +msgctxt "#30286" +msgid "Movies - Unwatched" +msgstr "" + diff --git a/plugin.video.embycon/resources/lib/__init__.py b/plugin.video.embycon/resources/lib/__init__.py new file mode 100644 index 0000000..b93054b --- /dev/null +++ b/plugin.video.embycon/resources/lib/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/plugin.video.embycon/resources/lib/clientinfo.py b/plugin.video.embycon/resources/lib/clientinfo.py new file mode 100644 index 0000000..273db01 --- /dev/null +++ b/plugin.video.embycon/resources/lib/clientinfo.py @@ -0,0 +1,46 @@ +# Gnu General Public License - see LICENSE.TXT + +from uuid import uuid4 as uuid4 +import xbmcaddon +import xbmc +import xbmcvfs + +from kodi_utils import HomeWindow +from simple_logging import SimpleLogging + +log = SimpleLogging(__name__) +__addon__ = xbmcaddon.Addon(id="plugin.video.embycon") + + +class ClientInformation(): + def getDeviceId(self): + + WINDOW = HomeWindow() + client_id = WINDOW.getProperty("client_id") + + if client_id: + return client_id + + emby_guid_path = xbmc.translatePath("special://temp/embycon_guid").decode('utf-8') + log.debug("emby_guid_path: " + emby_guid_path) + guid = xbmcvfs.File(emby_guid_path) + client_id = guid.read() + guid.close() + + if not client_id: + client_id = str("%012X" % uuid4()) + log.debug("Generating a new guid: " + client_id) + guid = xbmcvfs.File(emby_guid_path, 'w') + guid.write(client_id) + guid.close() + log.debug("emby_guid_path (NEW): " + client_id) + + WINDOW.setProperty("client_id", client_id) + return client_id + + def getVersion(self): + version = __addon__.getAddonInfo("version") + return version + + def getClient(self): + return 'Kodi EmbyCon' diff --git a/plugin.video.embycon/resources/lib/datamanager.py b/plugin.video.embycon/resources/lib/datamanager.py new file mode 100644 index 0000000..a94a38b --- /dev/null +++ b/plugin.video.embycon/resources/lib/datamanager.py @@ -0,0 +1,159 @@ +# Gnu General Public License - see LICENSE.TXT + +import hashlib +import os +import threading +import json + +import xbmcaddon +import xbmc + +from downloadutils import DownloadUtils +from simple_logging import SimpleLogging +from utils import getChecksum +from kodi_utils import HomeWindow + +log = SimpleLogging(__name__) + + +class DataManager(): + cacheDataResult = None + dataUrl = None + cacheDataPath = None + canRefreshNow = False + + def __init__(self, *args): + log.debug("DataManager __init__") + + def getCacheValidatorFromData(self, result): + key = 'Items' + results = result.get(key) + if results is None: + key = 'SearchHints' + results = result.get(key) + if results is None: + results = [] + + itemCount = 0 + dataHashString = "" + + for item in results: + item_hash_string = getChecksum(item) + item_hash_string = str(itemCount) + "_" + key + "_" + item.get("Name", "-") + "_" + item_hash_string + "|" + log.debug("ITEM_HASH: " + item_hash_string) + dataHashString += item_hash_string + itemCount += 1 + + # hash the data + dataHashString = dataHashString.encode("UTF-8") + m = hashlib.md5() + m.update(dataHashString) + validatorString = m.hexdigest() + + log.debug("getCacheValidatorFromData : RawData : " + dataHashString) + log.debug("getCacheValidatorFromData : hashData : " + validatorString) + + return validatorString + + def loadJasonData(self, jsonData): + return json.loads(jsonData) + + def GetContent(self, url): + + __addon__ = xbmcaddon.Addon(id='plugin.video.embycon') + use_cache_system = __addon__.getSetting('cacheEmbyData') == "true" + + if use_cache_system == False: + # dont use cache system at all, just get the result and return + log.debug("GetContent - Not using cache system") + jsonData = DownloadUtils().downloadUrl(url, suppress=False, popup=1) + result = self.loadJasonData(jsonData) + log.debug("Returning Loaded Result") + return result + + # first get the url hash + m = hashlib.md5() + m.update(url) + urlHash = m.hexdigest() + + # build cache data path + + __addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile')) + if not os.path.exists(os.path.join(__addondir__, "cache")): + os.makedirs(os.path.join(__addondir__, "cache")) + cacheDataPath = os.path.join(__addondir__, "cache", urlHash) + + log.debug("Cache_Data_Manager:" + cacheDataPath) + + # are we forcing a reload + WINDOW = HomeWindow() + force_data_reload = WINDOW.getProperty("force_data_reload") == "true" + WINDOW.clearProperty("force_data_reload") + + if os.path.exists(cacheDataPath) and not force_data_reload: + # load data from cache if it is available and trigger a background + # verification process to test cache validity + log.debug("Loading Cached File") + with open(cacheDataPath, 'r') as f: + result = self.loadJasonData(f.read()) + + # start a worker thread to process the cache validity + self.cacheDataResult = result + self.dataUrl = url + self.cacheDataPath = cacheDataPath + actionThread = CacheManagerThread() + actionThread.setCacheData(self) + actionThread.start() + + log.debug("Returning Cached Result") + return result + else: + # no cache data so load the url and save it + jsonData = DownloadUtils().downloadUrl(url, suppress=False, popup=1) + log.debug("Loading URL and saving to cache") + with open(cacheDataPath, 'w') as f: + f.write(jsonData) + result = self.loadJasonData(jsonData) + self.cacheManagerFinished = True + log.debug("Returning Loaded Result") + return result + + +class CacheManagerThread(threading.Thread): + dataManager = None + + def __init__(self, *args): + threading.Thread.__init__(self, *args) + + def setCacheData(self, data): + self.dataManager = data + + def run(self): + + log.debug("CacheManagerThread Started") + + cacheValidatorString = self.dataManager.getCacheValidatorFromData(self.dataManager.cacheDataResult) + log.debug("Cache Validator String (" + cacheValidatorString + ")") + + jsonData = DownloadUtils().downloadUrl(self.dataManager.dataUrl, suppress=False, popup=1) + loadedResult = self.dataManager.loadJasonData(jsonData) + loadedValidatorString = self.dataManager.getCacheValidatorFromData(loadedResult) + log.debug("Loaded Validator String (" + loadedValidatorString + ")") + + # if they dont match then save the data and trigger a content reload + if (cacheValidatorString != loadedValidatorString): + log.debug("CacheManagerThread Saving new cache data and reloading container") + with open(self.dataManager.cacheDataPath, 'w') as f: + f.write(jsonData) + + # we need to refresh but will wait until the main function has finished + loops = 0 + while (self.dataManager.canRefreshNow == False and loops < 200 and not xbmc.Monitor().abortRequested()): + log.debug("Cache_Data_Manager: Not finished yet") + xbmc.sleep(100) + loops = loops + 1 + + log.debug("Sending container refresh (" + str(loops) + ")") + xbmc.executebuiltin("Container.Refresh") + + log.debug("CacheManagerThread Exited") diff --git a/plugin.video.embycon/resources/lib/downloadutils.py b/plugin.video.embycon/resources/lib/downloadutils.py new file mode 100644 index 0000000..3cb7149 --- /dev/null +++ b/plugin.video.embycon/resources/lib/downloadutils.py @@ -0,0 +1,401 @@ +# Gnu General Public License - see LICENSE.TXT + +import xbmc +import xbmcgui +import xbmcaddon + +import httplib +import hashlib +import ssl +import StringIO +import gzip +import json + +from kodi_utils import HomeWindow +from clientinfo import ClientInformation +from simple_logging import SimpleLogging +from translation import i18n + +log = SimpleLogging(__name__) + + +class DownloadUtils(): + getString = None + + def __init__(self, *args): + addon = xbmcaddon.Addon(id='plugin.video.embycon') + self.addon_name = addon.getAddonInfo('name') + + def getServer(self): + settings = xbmcaddon.Addon(id='plugin.video.embycon') + host = settings.getSetting('ipaddress') + port = settings.getSetting('port') + if (len(host) == 0) or (host == "<none>") or (len(port) == 0): + return None + + server = host + ":" + port + use_https = settings.getSetting('use_https') == 'true' + if use_https: + server = "https://" + server + else: + server = "http://" + server + + return server + + def getArtwork(self, data, art_type, parent=False, index="0", width=10000, height=10000, server=None): + + id = data.get("Id") + ''' + if data.get("Type") == "Season": # For seasons: primary (poster), thumb and banner get season art, rest series art + if art_type != "Primary" and art_type != "Thumb" and art_type != "Banner": + id = data.get("SeriesId") + ''' + if data.get("Type") == "Episode": # For episodes: primary (episode thumb) gets episode art, rest series art. + if art_type != "Primary" or parent == True: + id = data.get("SeriesId") + + imageTag = "" + # "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format + + itemType = data.get("Type") + + # for episodes always use the parent BG + if (itemType == "Episode" and art_type == "Backdrop"): + id = data.get("ParentBackdropItemId") + bgItemTags = data.get("ParentBackdropImageTags") + if (bgItemTags != None and len(bgItemTags) > 0): + imageTag = bgItemTags[0] + elif (art_type == "Backdrop") and (parent == True): + id = data.get("ParentBackdropItemId") + bgItemTags = data.get("ParentBackdropImageTags") + if (bgItemTags != None and len(bgItemTags) > 0): + imageTag = bgItemTags[0] + elif (art_type == "Backdrop"): + BGTags = data.get("BackdropImageTags") + if (BGTags != None and len(BGTags) > 0): + bgIndex = int(index) + imageTag = data.get("BackdropImageTags")[bgIndex] + log.debug("Background Image Tag:" + imageTag) + elif (parent == False): + if (data.get("ImageTags") != None and data.get("ImageTags").get(art_type) != None): + imageTag = data.get("ImageTags").get(art_type) + log.debug("Image Tag:" + imageTag) + elif (parent == True): + if (itemType == "Episode") and (art_type == 'Primary'): + tagName = 'SeriesPrimaryImageTag' + idName = 'SeriesId' + else: + tagName = 'Parent%sTag' % art_type + idName = 'Parent%sItemId' % art_type + if (data.get(idName) != None and data.get(tagName) != None): + id = data.get(idName) + imageTag = data.get(tagName) + log.debug("Parent Image Tag:" + imageTag) + + if (imageTag == "" or imageTag == None) and (art_type != 'Banner'): # ParentTag not passed for Banner + log.debug("No Image Tag for request:" + art_type + " item:" + itemType + " parent:" + str(parent)) + return "" + + query = "" + + artwork = "%s/emby/Items/%s/Images/%s/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, art_type, index, width, height, imageTag, query) + + log.debug("getArtwork : " + artwork) + + ''' + # do not return non-existing images + if ( (art_type != "Backdrop" and imageTag == "") | + (art_type == "Backdrop" and data.get("BackdropImageTags") != None and len(data.get("BackdropImageTags")) == 0) | + (art_type == "Backdrop" and data.get("BackdropImageTag") != None and len(data.get("BackdropImageTag")) == 0) + ): + artwork = '' + ''' + + return artwork + + def imageUrl(self, id, art_type, index, width, height, imageTag, server): + + # test imageTag e3ab56fe27d389446754d0fb04910a34 + artwork = "%s/emby/Items/%s/Images/%s/%s?Format=original&Tag=%s" % (server, id, art_type, index, imageTag) + if int(width) > 0: + artwork += '&MaxWidth=%s' % width + if int(height) > 0: + artwork += '&MaxHeight=%s' % height + return artwork + + def getUserId(self): + + WINDOW = HomeWindow() + userid = WINDOW.getProperty("userid") + + if (userid != None and userid != ""): + log.debug("EmbyCon DownloadUtils -> Returning saved UserID : " + userid) + return userid + + settings = xbmcaddon.Addon('plugin.video.embycon') + userName = settings.getSetting('username') + + if not userName: + return "" + log.debug("Looking for user name: " + userName) + + jsonData = None + try: + jsonData = self.downloadUrl("{server}/emby/Users/Public?format=json", suppress=True, authenticate=False) + except Exception, msg: + error = "Get User unable to connect: " + str(msg) + log.error(error) + return "" + + log.debug("GETUSER_JSONDATA_01:" + str(jsonData)) + + result = [] + + try: + result = json.loads(jsonData) + except Exception, e: + log.debug("jsonload : " + str(e) + " (" + jsonData + ")") + return "" + + if result is None: + return "" + + log.debug("GETUSER_JSONDATA_02:" + str(result)) + + userid = "" + secure = False + for user in result: + if (user.get("Name") == userName): + userid = user.get("Id") + log.debug("Username Found:" + user.get("Name")) + if (user.get("HasPassword") == True): + secure = True + log.debug("Username Is Secure (HasPassword=True)") + break + + if (secure) or (not userid): + authOk = self.authenticate() + if (authOk == ""): + xbmcgui.Dialog().ok(self.addon_name, i18n('incorrect_user_pass')) + return "" + if not userid: + userid = WINDOW.getProperty("userid") + + if userid == "": + xbmcgui.Dialog().ok(self.addon_name, i18n('username_not_found')) + + log.debug("userid : " + userid) + + WINDOW.setProperty("userid", userid) + + return userid + + def authenticate(self): + + WINDOW = HomeWindow() + + token = WINDOW.getProperty("AccessToken") + if (token != None and token != ""): + log.debug("EmbyCon DownloadUtils -> Returning saved AccessToken : " + token) + return token + + settings = xbmcaddon.Addon('plugin.video.embycon') + port = settings.getSetting("port") + host = settings.getSetting("ipaddress") + if (host == None or host == "" or port == None or port == ""): + return "" + + url = "{server}/emby/Users/AuthenticateByName?format=json" + + clientInfo = ClientInformation() + txt_mac = clientInfo.getDeviceId() + version = clientInfo.getVersion() + client = clientInfo.getClient() + + deviceName = settings.getSetting('deviceName') + deviceName = deviceName.replace("\"", "_") + + authString = "Mediabrowser Client=\"" + client + "\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" + headers = {'Accept-encoding': 'gzip', 'Authorization': authString} + sha1 = hashlib.sha1(settings.getSetting('password')) + + messageData = "username=" + settings.getSetting('username') + "&password=" + sha1.hexdigest() + + resp = self.downloadUrl(url, postBody=messageData, method="POST", suppress=True, authenticate=False) + + accessToken = None + userid = None + try: + result = json.loads(resp) + accessToken = result.get("AccessToken") + userid = result["SessionInfo"].get("UserId") + except: + pass + + if (accessToken != None): + log.debug("User Authenticated : " + accessToken) + WINDOW.setProperty("AccessToken", accessToken) + WINDOW.setProperty("userid", userid) + return accessToken + else: + log.debug("User NOT Authenticated") + WINDOW.setProperty("AccessToken", "") + WINDOW.setProperty("userid", "") + return "" + + def getAuthHeader(self, authenticate=True): + clientInfo = ClientInformation() + txt_mac = clientInfo.getDeviceId() + version = clientInfo.getVersion() + client = clientInfo.getClient() + + settings = xbmcaddon.Addon('plugin.video.embycon') + deviceName = settings.getSetting('deviceName') + deviceName = deviceName.replace("\"", "_") + + headers = {} + headers["Accept-encoding"] = "gzip" + headers["Accept-Charset"] = "UTF-8,*" + + if (authenticate == False): + authString = "MediaBrowser Client=\"" + client + "\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" + headers["Authorization"] = authString + headers['X-Emby-Authorization'] = authString + return headers + else: + userid = self.getUserId() + authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"" + client + "\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" + headers["Authorization"] = authString + headers['X-Emby-Authorization'] = authString + + authToken = self.authenticate() + if (authToken != ""): + headers["X-MediaBrowser-Token"] = authToken + + log.debug("EmbyCon Authentication Header : " + str(headers)) + return headers + + def downloadUrl(self, url, suppress=False, postBody=None, method="GET", popup=0, authenticate=True): + log.debug("downloadUrl") + settings = xbmcaddon.Addon(id='plugin.video.embycon') + + log.debug(url) + if url.find("{server}") != -1: + server = self.getServer() + url = url.replace("{server}", server) + if url.find("{userid}") != -1: + userid = self.getUserId() + url = url.replace("{userid}", userid) + if url.find("{ItemLimit}") != -1: + show_x_filtered_items = settings.getSetting("show_x_filtered_items") + url = url.replace("{ItemLimit}", show_x_filtered_items) + log.debug(url) + + return_data = "null" + try: + if url.startswith('http'): + serversplit = 2 + urlsplit = 3 + else: + serversplit = 0 + urlsplit = 1 + + server = url.split('/')[serversplit] + urlPath = "/" + "/".join(url.split('/')[urlsplit:]) + + log.debug("DOWNLOAD_URL = " + url) + log.debug("server = " + str(server)) + log.debug("urlPath = " + str(urlPath)) + + # check the server details + tokens = server.split(':') + host = tokens[0] + port = tokens[1] + if (host == "<none>" or host == "" or port == ""): + return "" + + settings = xbmcaddon.Addon('plugin.video.embycon') + use_https = settings.getSetting('use_https') == 'true' + verify_cert = settings.getSetting('verify_cert') == 'true' + + if use_https and verify_cert: + log.debug("Connection: HTTPS, Cert checked") + conn = httplib.HTTPSConnection(server, timeout=40) + elif use_https and not verify_cert: + log.debug("Connection: HTTPS, Cert NOT checked") + conn = httplib.HTTPSConnection(server, timeout=40, context=ssl._create_unverified_context()) + else: + log.debug("Connection: HTTP") + conn = httplib.HTTPConnection(server, timeout=40) + + head = self.getAuthHeader(authenticate) + log.debug("HEADERS : " + str(head)) + + if (postBody != None): + if isinstance(postBody, dict): + content_type = "application/json" + postBody = json.dumps(postBody) + else: + content_type = "application/x-www-form-urlencoded" + + head["Content-Type"] = content_type + log.debug("Content-Type : " + content_type) + + log.debug("POST DATA : " + postBody) + conn.request(method=method, url=urlPath, body=postBody, headers=head) + else: + conn.request(method=method, url=urlPath, headers=head) + + data = conn.getresponse() + log.debug("GET URL HEADERS : " + str(data.getheaders())) + + if int(data.status) == 200: + retData = data.read() + contentType = data.getheader('content-encoding') + log.debug("Data Len Before : " + str(len(retData))) + if (contentType == "gzip"): + retData = StringIO.StringIO(retData) + gzipper = gzip.GzipFile(fileobj=retData) + return_data = gzipper.read() + else: + return_data = retData + log.debug("Data Len After : " + str(len(return_data))) + log.debug("====== 200 returned =======") + log.debug("Content-Type : " + str(contentType)) + log.debug(return_data) + log.debug("====== 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) + log.error(error) + if suppress is False: + if popup == 0: + xbmcgui.Dialog().notification(self.addon_name, i18n('url_error_') + str(data.reason)) + else: + xbmcgui.Dialog().ok(self.addon_name, i18n('url_error_') % str(data.reason)) + log.error(error) + + except Exception, msg: + error = "Unable to connect to " + str(server) + " : " + str(msg) + log.error(error) + if suppress is False: + if popup == 0: + xbmcgui.Dialog().notification(self.addon_name, i18n('url_error_') + str(msg)) + else: + xbmcgui.Dialog().ok(self.addon_name, i18n('url_error_') % i18n('unable_connect_server'), str(msg)) + #raise + finally: + try: + log.debug("Closing HTTP connection: " + str(conn)) + conn.close() + except: + pass + + return return_data diff --git a/plugin.video.embycon/resources/lib/functions.py b/plugin.video.embycon/resources/lib/functions.py new file mode 100644 index 0000000..168800c --- /dev/null +++ b/plugin.video.embycon/resources/lib/functions.py @@ -0,0 +1,1332 @@ +# Gnu General Public License - see LICENSE.TXT + +import urllib +import sys +import os +import time +import cProfile +import pstats +import json +import StringIO + +import xbmcplugin +import xbmcgui +import xbmcaddon +import xbmc + +from downloadutils import DownloadUtils +from utils import getDetailsString, getArt +from kodi_utils import HomeWindow +from clientinfo import ClientInformation +from datamanager import DataManager +from server_detect import checkServer +from simple_logging import SimpleLogging +from menu_functions import displaySections, showMovieAlphaList, showGenreList, showWidgets, showSearch +from translation import i18n +from server_sessions import showServerSessions + +__addon__ = xbmcaddon.Addon(id='plugin.video.embycon') +__addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile')) +__cwd__ = __addon__.getAddonInfo('path') +PLUGINPATH = xbmc.translatePath(os.path.join(__cwd__)) + +log = SimpleLogging(__name__) + +kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) + +downloadUtils = DownloadUtils() +dataManager = DataManager() + + +def mainEntryPoint(): + log.debug("===== EmbyCon START =====") + + settings = xbmcaddon.Addon(id='plugin.video.embycon') + profile_code = settings.getSetting('profile') == "true" + pr = None + if (profile_code): + return_value = xbmcgui.Dialog().yesno("Profiling Enabled", "Do you want to run profiling?") + if return_value: + pr = cProfile.Profile() + pr.enable() + + ADDON_VERSION = ClientInformation().getVersion() + log.debug("Running Python: " + str(sys.version_info)) + log.debug("Running EmbyCon: " + str(ADDON_VERSION)) + log.debug("Kodi BuildVersion: " + xbmc.getInfoLabel("System.BuildVersion")) + log.debug("Kodi Version: " + str(kodi_version)) + log.debug("Script argument data: " + str(sys.argv)) + + try: + params = get_params(sys.argv[2]) + except: + params = {} + + if (len(params) == 0): + home_window = HomeWindow() + windowParams = home_window.getProperty("Params") + log.debug("windowParams : " + windowParams) + # home_window.clearProperty("Params") + if (windowParams): + try: + params = get_params(windowParams) + except: + params = {} + + log.debug("Script params = " + str(params)) + + param_url = params.get('url', None) + + if param_url: + param_url = urllib.unquote(param_url) + + mode = params.get("mode", None) + home_window = HomeWindow() + + if mode == "CHANGE_USER": + checkServer(change_user=True, notify=False) + elif mode == "DETECT_SERVER": + checkServer(force=True, notify=True) + elif mode == "DETECT_SERVER_USER": + checkServer(force=True, change_user=True, notify=False) + elif sys.argv[1] == "markWatched": + item_id = sys.argv[2] + markWatched(item_id) + elif sys.argv[1] == "markUnwatched": + item_id = sys.argv[2] + markUnwatched(item_id) + elif sys.argv[1] == "markFavorite": + item_id = sys.argv[2] + markFavorite(item_id) + elif sys.argv[1] == "unmarkFavorite": + item_id = sys.argv[2] + unmarkFavorite(item_id) + elif sys.argv[1] == "delete": + item_id = sys.argv[2] + delete(item_id) + elif mode == "MOVIE_ALPHA": + showMovieAlphaList() + elif mode == "MOVIE_GENRA": + showGenreList() + elif mode == "WIDGETS": + showWidgets() + elif mode == "SHOW_SETTINGS": + __addon__.openSettings() + WINDOW = xbmcgui.getCurrentWindowId() + if WINDOW == 10000: + log.debug("Currently in home - refreshing to allow new settings to be taken") + xbmc.executebuiltin("ActivateWindow(Home)") + elif sys.argv[1] == "refresh": + home_window = HomeWindow() + home_window.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + elif mode == "WIDGET_CONTENT": + getWigetContent(int(sys.argv[1]), params) + elif mode == "PARENT_CONTENT": + checkService() + checkServer(notify=False) + showParentContent(sys.argv[0], int(sys.argv[1]), params) + elif mode == "SHOW_CONTENT": + # plugin://plugin.video.embycon?mode=SHOW_CONTENT&item_type=Movie|Series + checkService() + checkServer(notify=False) + showContent(sys.argv[0], int(sys.argv[1]), params) + elif mode == "SEARCH": + # plugin://plugin.video.embycon?mode=SEARCH + checkService() + checkServer(notify=False) + xbmcplugin.setContent(int(sys.argv[1]), 'files') + showSearch() + elif mode == "NEW_SEARCH": + # plugin://plugin.video.embycon?mode=NEW_SEARCH&item_type=<Movie|Series|Episode> + if 'SEARCH_RESULTS' not in xbmc.getInfoLabel('Container.FolderPath'): # don't ask for input on '..' + checkService() + checkServer(notify=False) + search(int(sys.argv[1]), params) + else: + return + elif mode == "SEARCH_RESULTS": + # plugin://plugin.video.embycon?mode=SEARCH_RESULTS&item_type=<Movie|Series>&query=<urllib.quote(search query)>&index=<[0-9]+> + checkService() + checkServer(notify=False) + searchResults(params) + elif mode == "SHOW_SERVER_SESSIONS": + checkService() + checkServer(notify=False) + showServerSessions() + else: + + checkService() + checkServer(notify=False) + + pluginhandle = int(sys.argv[1]) + + log.debug("EmbyCon -> Mode: " + str(mode)) + log.debug("EmbyCon -> URL: " + str(param_url)) + + # Run a function based on the mode variable that was passed in the URL + # if ( mode == None or param_url == None or len(param_url) < 1 ): + # displaySections(pluginhandle) + if mode == "GET_CONTENT": + getContent(param_url, params) + + elif mode == "PLAY": + PLAY(params, pluginhandle) + + else: + displaySections() + + dataManager.canRefreshNow = True + + if (pr): + pr.disable() + + fileTimeStamp = time.strftime("%Y%m%d-%H%M%S") + tabFileName = __addondir__ + "profile(" + fileTimeStamp + ").txt" + s = StringIO.StringIO() + ps = pstats.Stats(pr, stream=s) + ps = ps.sort_stats('cumulative') + ps.print_stats() + ps.strip_dirs() + ps = ps.sort_stats('tottime') + ps.print_stats() + with open(tabFileName, 'wb') as f: + f.write(s.getvalue()) + + ''' + ps = pstats.Stats(pr) + 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() + + log.debug("===== EmbyCon FINISHED =====") + + +def markWatched(item_id): + log.debug("Mark Item Watched : " + item_id) + url = "{server}/emby/Users/{userid}/PlayedItems/" + item_id + downloadUtils.downloadUrl(url, postBody="", method="POST") + home_window = HomeWindow() + home_window.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + + +def markUnwatched(item_id): + log.debug("Mark Item UnWatched : " + item_id) + url = "{server}/emby/Users/{userid}/PlayedItems/" + item_id + downloadUtils.downloadUrl(url, method="DELETE") + home_window = HomeWindow() + home_window.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + + +def markFavorite(item_id): + log.debug("Add item to favourites : " + item_id) + url = "{server}/emby/Users/{userid}/FavoriteItems/" + item_id + downloadUtils.downloadUrl(url, postBody="", method="POST") + home_window = HomeWindow() + home_window.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + + +def unmarkFavorite(item_id): + log.debug("Remove item from favourites : " + item_id) + url = "{server}/emby/Users/{userid}/FavoriteItems/" + item_id + downloadUtils.downloadUrl(url, method="DELETE") + home_window = HomeWindow() + home_window.setProperty("force_data_reload", "true") + xbmc.executebuiltin("Container.Refresh") + + +def delete(item_id): + return_value = xbmcgui.Dialog().yesno(i18n('confirm_file_delete'), i18n('file_delete_confirm')) + if return_value: + log.debug('Deleting Item : ' + item_id) + url = '{server}/emby/Items/' + item_id + progress = xbmcgui.DialogProgress() + progress.create(i18n('deleting'), i18n('waiting_server_delete')) + downloadUtils.downloadUrl(url, method="DELETE") + progress.close() + xbmc.executebuiltin("Container.Refresh") + + +def addGUIItem(url, details, extraData, display_options, folder=True): + + url = url.encode('utf-8') + + log.debug("Adding GuiItem for [%s]" % details.get('title', i18n('unknown'))) + log.debug("Passed details: " + str(details)) + log.debug("Passed extraData: " + str(extraData)) + + if details.get('title', '') == '': + return + + if extraData.get('mode', None) is None: + mode = "&mode=0" + else: + mode = "&mode=%s" % extraData['mode'] + + # Create the URL to pass to the item + if folder: + u = sys.argv[0] + "?url=" + urllib.quote(url) + mode + "&media_type=" + extraData["itemtype"] + else: + u = sys.argv[0] + "?item_id=" + url + "&mode=PLAY" + + # Create the ListItem that will be displayed + thumbPath = str(extraData.get('thumb', '')) + + listItemName = details.get('title', i18n('unknown')) + + # 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')) + percentage = int((resume / duration) * 100.0) + cappedPercentage = percentage + + if (extraData.get('TotalEpisodes') != None and extraData.get('TotalEpisodes') != "0"): + totalItems = int(extraData.get('TotalEpisodes')) + watched = int(extraData.get('WatchedEpisodes')) + percentage = int((float(watched) / float(totalItems)) * 100.0) + cappedPercentage = percentage + if (cappedPercentage == 0): + cappedPercentage = None + if (cappedPercentage == 100): + cappedPercentage = None + + countsAdded = False + addCounts = display_options.get("addCounts", True) + if addCounts and extraData.get('UnWatchedEpisodes') != "0": + countsAdded = True + listItemName = listItemName + " (" + extraData.get('UnWatchedEpisodes') + ")" + + addResumePercent = display_options.get("addResumePercent", True) + if (countsAdded == False and addResumePercent and details.get('title') != None and cappedPercentage != None): + listItemName = listItemName + " (" + str(cappedPercentage) + "%)" + + subtitle_available = display_options.get("addSubtitleAvailable", False) + if subtitle_available and extraData.get("SubtitleAvailable", False): + listItemName += " (cc)" + + # update title with new name, this sets the new name in the deailts that are later passed to video info + details['title'] = listItemName + + if kodi_version > 17: + list_item = xbmcgui.ListItem(listItemName, offscreen=True) + else: + list_item = xbmcgui.ListItem(listItemName, iconImage=thumbPath, thumbnailImage=thumbPath) + + log.debug("Setting thumbnail as " + thumbPath) + + # calculate percentage + if (cappedPercentage != None): + list_item.setProperty("complete_percentage", str(cappedPercentage)) + + # For all end items + if (not folder): + # list_item.setProperty('IsPlayable', 'true') + if extraData.get('type', 'video').lower() == "video": + list_item.setProperty('TotalTime', str(extraData.get('duration'))) + list_item.setProperty('ResumeTime', str(int(extraData.get('resumetime')))) + + # StartPercent + + artTypes = ['thumb', 'poster', 'fanart', 'clearlogo', 'discart', 'banner', 'clearart', 'landscape', 'tvshow.poster'] + artLinks = {} + for artType in artTypes: + artLinks[artType] = extraData.get(artType, '') + log.debug("Setting " + artType + " as " + artLinks[artType]) + list_item.setProperty('fanart_image', artLinks['fanart']) # back compat + list_item.setProperty('discart', artLinks['discart']) # not avail to setArt + list_item.setProperty('tvshow.poster', artLinks['tvshow.poster']) # not avail to setArt + list_item.setArt(artLinks) + + menuItems = addContextMenu(details, extraData, folder) + if (len(menuItems) > 0): + list_item.addContextMenuItems(menuItems, True) + + # new way + videoInfoLabels = {} + + # add cast + people = extraData.get('cast') + if people is not None: + if kodi_version >= 17: + list_item.setCast(people) + else: + videoInfoLabels['cast'] = videoInfoLabels['castandrole'] = [(cast_member['name'], cast_member['role']) for cast_member in people] + + if (extraData.get('type') == None or extraData.get('type') == "Video"): + videoInfoLabels.update(details) + else: + list_item.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') + + item_type = extraData.get('itemtype').lower() + mediatype = 'video' + + if (item_type == 'movie') or (item_type == 'boxset'): + mediatype = 'movie' + elif (item_type == 'series'): + mediatype = 'tvshow' + elif (item_type == 'season'): + mediatype = 'season' + elif (item_type == 'episode'): + mediatype = 'episode' + + videoInfoLabels["mediatype"] = mediatype + + if mediatype == 'episode': + videoInfoLabels["episode"] = details.get('episode') + + if (mediatype == 'season') or (mediatype == 'episode'): + videoInfoLabels["season"] = details.get('season') + + list_item.setInfo('video', videoInfoLabels) + + list_item.addStreamInfo('video', + {'duration': extraData.get('duration'), 'aspect': extraData.get('aspectratio'), + 'codec': extraData.get('videocodec'), 'width': extraData.get('width'), + 'height': extraData.get('height')}) + list_item.addStreamInfo('audio', {'codec': extraData.get('audiocodec'), 'channels': extraData.get('channels')}) + if extraData.get('SubtitleLang', '') != '': + list_item.addStreamInfo('subtitle', {'language': extraData.get('SubtitleLang', '')}) + + list_item.setProperty('CriticRating', str(extraData.get('criticrating'))) + list_item.setProperty('ItemType', extraData.get('itemtype')) + + if extraData.get('totaltime') != None: + list_item.setProperty('TotalTime', extraData.get('totaltime')) + if extraData.get('TotalSeasons') != None: + list_item.setProperty('TotalSeasons', extraData.get('TotalSeasons')) + if extraData.get('TotalEpisodes') != None: + list_item.setProperty('TotalEpisodes', extraData.get('TotalEpisodes')) + if extraData.get('WatchedEpisodes') != None: + list_item.setProperty('WatchedEpisodes', extraData.get('WatchedEpisodes')) + if extraData.get('UnWatchedEpisodes') != None: + list_item.setProperty('UnWatchedEpisodes', extraData.get('UnWatchedEpisodes')) + if extraData.get('NumEpisodes') != None: + list_item.setProperty('NumEpisodes', extraData.get('NumEpisodes')) + + #list_item.setProperty('ItemGUID', extraData.get('guiid')) + list_item.setProperty('id', extraData.get('id')) + + return (u, list_item, folder) + + +def addContextMenu(details, extraData, folder): + commands = [] + + item_id = extraData.get('id') + if item_id != None: + scriptToRun = PLUGINPATH + "/default.py" + + if not folder: + argsToPass = "?mode=PLAY&item_id=" + item_id + "&force_transcode=true" + commands.append((i18n('emby_force_transcode'), "RunPlugin(plugin://plugin.video.embycon" + argsToPass + ")")) + + # watched/unwatched + if extraData.get("playcount") == "0": + argsToPass = 'markWatched,' + item_id + commands.append((i18n('emby_mark_watched'), "RunScript(" + scriptToRun + ", " + argsToPass + ")")) + elif extraData.get("playcount"): + argsToPass = 'markUnwatched,' + item_id + commands.append((i18n('emby_mark_unwatched'), "RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + # favourite add/remove + if extraData.get('favorite') == 'false': + argsToPass = 'markFavorite,' + item_id + commands.append((i18n('emby_set_favorite'), "RunScript(" + scriptToRun + ", " + argsToPass + ")")) + elif extraData.get('favorite') == 'true': + argsToPass = 'unmarkFavorite,' + item_id + commands.append((i18n('emby_unset_favorite'), "RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + # delete + argsToPass = 'delete,' + item_id + commands.append((i18n('emby_delete'), "RunScript(" + scriptToRun + ", " + argsToPass + ")")) + + return (commands) + + +def get_params(paramstring): + log.debug("Parameter string: " + paramstring) + 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] + + log.debug("EmbyCon -> Detected parameters: " + str(param)) + return param + + +def setSort(pluginhandle, viewType): + log.debug("SETTING_SORT for media type: " + str(viewType)) + if viewType == "BoxSets": + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_YEAR) + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE) + else: + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE) + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_YEAR) + + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_GENRE) + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_UNSORTED) + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_NONE) + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_VIDEO_RATING) + xbmcplugin.addSortMethod(pluginhandle, xbmcplugin.SORT_METHOD_LABEL) + +def getContent(url, params): + log.debug("== ENTER: getContent ==") + + media_type = params.get("media_type", None) + if not media_type: + xbmcgui.Dialog().ok(i18n('error'), i18n('no_media_type')) + + log.debug("URL: " + str(url)) + log.debug("MediaType: " + str(media_type)) + pluginhandle = int(sys.argv[1]) + + settings = xbmcaddon.Addon(id='plugin.video.embycon') + # determine view type, map it from media type to view type + viewType = "" + media_type = str(media_type).lower().strip() + if media_type.startswith("movie"): + viewType = "Movies" + xbmcplugin.setContent(pluginhandle, 'movies') + elif media_type.startswith("boxset"): + viewType = "BoxSets" + xbmcplugin.setContent(pluginhandle, 'movies') + elif media_type == "tvshows": + viewType = "Series" + xbmcplugin.setContent(pluginhandle, 'tvshows') + elif media_type == "series": + viewType = "Seasons" + xbmcplugin.setContent(pluginhandle, 'seasons') + elif media_type == "season" or media_type == "episodes": + viewType = "Episodes" + xbmcplugin.setContent(pluginhandle, 'episodes') + log.debug("ViewType: " + viewType) + + setSort(pluginhandle, viewType) + + # show a progress indicator if needed + progress = None + if (settings.getSetting('showLoadProgress') == "true"): + progress = xbmcgui.DialogProgress() + progress.create(i18n('loading_content')) + progress.update(0, i18n('retrieving_data')) + + # use the data manager to get the data + result = dataManager.GetContent(url) + + if result == None or len(result) == 0: + if (progress != None): + progress.close() + return + + dirItems = processDirectory(result, progress, params) + if dirItems is None: + return + xbmcplugin.addDirectoryItems(pluginhandle, dirItems) + + xbmcplugin.endOfDirectory(pluginhandle, cacheToDisc=False) + + if (progress != None): + progress.update(100, i18n('done')) + progress.close() + + return + + +def processDirectory(results, progress, params): + log.debug("== ENTER: processDirectory ==") + + settings = xbmcaddon.Addon(id='plugin.video.embycon') + server = downloadUtils.getServer() + detailsString = getDetailsString() + + name_format = params.get("name_format", None) + if name_format is not None: + name_format = urllib.unquote(name_format) + name_format = settings.getSetting(name_format) + + dirItems = [] + result = results.get("Items") + if result is None: + result = [] + + # flatten single season + # if there is only one result and it is a season and you have flatten signle season turned on then + # build a new url, set the content media type and call get content again + flatten_single_season = settings.getSetting("flatten_single_season") == "true" + if flatten_single_season and len(result) == 1 and result[0].get("Type", "") == "Season": + season_id = result[0].get("Id") + season_url = ('{server}/emby/Users/{userid}/items' + + '?ParentId=' + season_id + + '&IsVirtualUnAired=false' + + '&IsMissing=false' + + '&Fields=' + detailsString + + '&format=json') + if (progress != None): + progress.close() + params["media_type"] = "Episodes" + getContent(season_url, params) + return None + + add_season_number = settings.getSetting('addSeasonNumber') == 'true' + add_episode_number = settings.getSetting('addEpisodeNumber') == 'true' + + display_options = {} + display_options["addCounts"] = settings.getSetting("addCounts") == 'true' + display_options["addResumePercent"] = settings.getSetting("addResumePercent") == 'true' + display_options["addSubtitleAvailable"] = settings.getSetting("addSubtitleAvailable") == 'true' + + 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), i18n('processing_item:') + str(current_item)) + current_item = current_item + 1 + + id = str(item.get("Id")).encode('utf-8') + isFolder = item.get("IsFolder") + + item_type = str(item.get("Type")).encode('utf-8') + + tempEpisode = item.get("IndexNumber") + if tempEpisode is not None: + if tempEpisode < 10: + tempEpisode = "0" + str(tempEpisode) + else: + tempEpisode = str(tempEpisode) + else: + tempEpisode = "" + + tempSeason = item.get("ParentIndexNumber") + if tempSeason is not None: + if tempSeason < 10: + tempSeason = "0" + str(tempSeason) + else: + tempSeason = str(tempSeason) + else: + tempSeason = "" + + # set the item name + # override with name format string from request + if name_format is not None: + nameInfo = {} + nameInfo["ItemName"] = item.get("Name", "").encode('utf-8') + nameInfo["SeriesName"] = item.get("SeriesName", "").encode('utf-8') + nameInfo["SeasonIndex"] = tempSeason + nameInfo["EpisodeIndex"] = tempEpisode + log.debug("FormatName : %s | %s" % (name_format, nameInfo)) + tempTitle = name_format.format(**nameInfo).strip() + + else: + if (item.get("Name") != None): + tempTitle = item.get("Name").encode('utf-8') + else: + tempTitle = i18n('missing_title') + + if item.get("Type") == "Episode": + prefix = '' + if add_season_number: + prefix = "S" + str(tempSeason) + if add_episode_number: + prefix = prefix + "E" + if add_episode_number: + prefix = prefix + str(tempEpisode) + if prefix != '': + tempTitle = prefix + ' - ' + tempTitle + + 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) + + # Process MediaStreams + channels = '' + videocodec = '' + audiocodec = '' + height = '' + width = '' + aspectfloat = 0.0 + subtitle_lang = '' + subtitle_available = False + 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 is not 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") + if mediaStream.get("Type") == "Subtitle": + subtitle_available = True + if mediaStream.get("Language") is not None: + subtitle_lang = mediaStream.get("Language") + + # Process People + director = '' + writer = '' + cast = None + people = item.get("People") + if (people != None): + cast = [] + 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"): + person_name = person.get("Name") + person_role = person.get("Role") + if person_role == None: + person_role = '' + person_id = person.get("Id") + person_tag = person.get("PrimaryImageTag") + person_thumbnail = downloadUtils.imageUrl(person_id, "Primary", 0, 400, 400, person_tag, server=server) + person = {"name": person_name, "role": person_role, "thumbnail": person_thumbnail} + cast.append(person) + + # 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") + 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: + 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"), + 'Overlay': overlay, + 'playcount': str(playCount), + # 'aired' : episode.get('originallyAvailableAt','') , + 'TVShowTitle': item.get("SeriesName"), + } + + if item_type == "Episode": + details['episode'] = tempEpisode + if item_type == "Episode" or item_type == "Season": + details['season'] = tempSeason + + try: + tempDuration = str(int(item.get("RunTimeTicks", "0")) / (10000000)) + except TypeError: + try: + tempDuration = str(int(item.get("CumulativeRunTimeTicks")) / (10000000)) + except TypeError: + tempDuration = "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 + + art = getArt(item, server) + # Populate the extraData list + extraData = {'thumb': art['thumb'], + 'fanart': art['fanart'], + 'poster': art['poster'], + 'banner': art['banner'], + 'clearlogo': art['clearlogo'], + 'discart': art['discart'], + 'clearart': art['clearart'], + 'landscape': art['landscape'], + 'tvshow.poster': art['tvshow.poster'], + 'id': id, + '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, + '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, + 'SubtitleLang': subtitle_lang, + 'SubtitleAvailable': subtitle_available} + + extraData["Path"] = item.get("Path") + + extraData['mode'] = "GET_CONTENT" + + if isFolder == True: + u = ('{server}/emby/Users/{userid}/items' + + '?ParentId=' + id + + '&IsVirtualUnAired=false' + + '&IsMissing=false&' + + 'Fields=' + detailsString + + '&format=json') + + if item.get("RecursiveItemCount") != 0: + dirItems.append(addGUIItem(u, details, extraData, display_options)) + else: + u = id + dirItems.append(addGUIItem(u, details, extraData, display_options, folder=False)) + + return dirItems + +def getWigetContent(handle, params): + log.debug("getWigetContent Called" + str(params)) + server = downloadUtils.getServer() + + type = params.get("type") + if (type == None): + log.error("getWigetContent type not set") + return + + itemsUrl = ("{server}/emby/Users/{userid}/Items" + "?Limit=20" + "&format=json" + "&ImageTypeLimit=1" + "&IsMissing=False") + + if (type == "recent_movies"): + xbmcplugin.setContent(handle, 'movies') + itemsUrl += ("&Recursive=true" + "&SortBy=DateCreated" + "&SortOrder=Descending" + "&Filters=IsUnplayed,IsNotFolder" + "&IsVirtualUnaired=false" + "&IsMissing=False" + "&IncludeItemTypes=Movie") + elif (type == "inprogress_movies"): + xbmcplugin.setContent(handle, 'movies') + itemsUrl += ("&Recursive=true" + "&SortBy=DatePlayed" + "&SortOrder=Descending" + "&Filters=IsResumable" + "&IsVirtualUnaired=false" + "&IsMissing=False" + "&IncludeItemTypes=Movie") + elif (type == "random_movies"): + xbmcplugin.setContent(handle, 'movies') + itemsUrl += ("&Recursive=true" + "&SortBy=Random" + "&SortOrder=Descending" + "&Filters=IsUnplayed,IsNotFolder" + "&IsVirtualUnaired=false" + "&IsMissing=False" + "&IncludeItemTypes=Movie") + elif (type == "recent_episodes"): + xbmcplugin.setContent(handle, 'episodes') + itemsUrl += ("&Recursive=true" + "&SortBy=DateCreated" + "&SortOrder=Descending" + "&Filters=IsUnplayed,IsNotFolder" + "&IsVirtualUnaired=false" + "&IsMissing=False" + "&IncludeItemTypes=Episode") + elif (type == "inprogress_episodes"): + xbmcplugin.setContent(handle, 'episodes') + itemsUrl += ("&Recursive=true" + "&SortBy=DatePlayed" + "&SortOrder=Descending" + "&Filters=IsResumable" + "&IsVirtualUnaired=false" + "&IsMissing=False" + "&IncludeItemTypes=Episode") + elif (type == "nextup_episodes"): + xbmcplugin.setContent(handle, 'episodes') + itemsUrl = ("{server}/emby/Shows/NextUp" + "?Limit=20" + "&userid={userid}" + "&Recursive=true" + "&format=json" + "&ImageTypeLimit=1") + + log.debug("WIDGET_DATE_URL: " + itemsUrl) + + # get the items + jsonData = downloadUtils.downloadUrl(itemsUrl, suppress=False, popup=1) + log.debug("Recent(Items) jsonData: " + jsonData) + result = json.loads(jsonData) + + if result is None: + return [] + + result = result.get("Items") + if (result == None): + result = [] + + itemCount = 1 + listItems = [] + for item in result: + item_id = item.get("Id") + name = item.get("Name") + episodeDetails = "" + log.debug("WIDGET_DATE_NAME: " + name) + + title = item.get("Name") + tvshowtitle = "" + + if (item.get("Type") == "Episode" and item.get("SeriesName") != None): + + eppNumber = "X" + tempEpisodeNumber = "0" + 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) + + episodeDetails = "S" + tempSeasonNumber + "E" + tempEpisodeNumber + name = item.get("SeriesName") + " " + episodeDetails + tvshowtitle = episodeDetails + title = item.get("SeriesName") + + art = getArt(item, server, widget=True) + + if kodi_version > 17: + list_item = xbmcgui.ListItem(label=name, iconImage=art['thumb'], offscreen=True) + else: + list_item = xbmcgui.ListItem(label=name, iconImage=art['thumb']) + + # list_item.setLabel2(episodeDetails) + list_item.setInfo(type="Video", infoLabels={"title": title, "tvshowtitle": tvshowtitle}) + list_item.setProperty('fanart_image', art['fanart']) # back compat + list_item.setProperty('discart', art['discart']) # not avail to setArt + list_item.setArt(art) + # add count + list_item.setProperty("item_index", str(itemCount)) + itemCount = itemCount + 1 + + list_item.setProperty('IsPlayable', 'true') + + totalTime = str(int(float(item.get("RunTimeTicks", "0")) / (10000000 * 60))) + list_item.setProperty('TotalTime', str(totalTime)) + + # add progress percent + userData = item.get("UserData") + if (userData != None): + playBackTicks = float(userData.get("PlaybackPositionTicks")) + if (playBackTicks != None and playBackTicks > 0): + runTimeTicks = float(item.get("RunTimeTicks", "0")) + if (runTimeTicks > 0): + playBackPos = int(((playBackTicks / 1000) / 10000) / 60) + list_item.setProperty('ResumeTime', str(playBackPos)) + + percentage = int((playBackTicks / runTimeTicks) * 100.0) + list_item.setProperty("complete_percentage", str(percentage)) + + playurl = "plugin://plugin.video.embycon/?item_id=" + item_id + '&mode=PLAY' + + itemTupple = (playurl, list_item, False) + listItems.append(itemTupple) + + xbmcplugin.addDirectoryItems(handle, listItems) + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) + + +def showContent(pluginName, handle, params): + log.debug("showContent Called: " + str(params)) + + item_type = params.get("item_type") + + contentUrl = ("{server}/emby/Users/{userid}/Items" + "?format=json" + "&ImageTypeLimit=1" + "&IsMissing=False" + "&Fields=" + getDetailsString() + + "&Recursive=true" + "&IsVirtualUnaired=false" + "&IsMissing=False" + "&IncludeItemTypes=" + item_type) + + log.debug("showContent Content Url : " + str(contentUrl)) + getContent(contentUrl, params) + +def showParentContent(pluginName, handle, params): + log.debug("showParentContent Called: " + str(params)) + + settings = xbmcaddon.Addon(id='plugin.video.embycon') + + parentId = params.get("ParentId") + detailsString = getDetailsString() + + contentUrl = ( + "{server}/emby/Users/{userid}/items?ParentId=" + parentId + + "&IsVirtualUnaired=false" + + "&IsMissing=False" + + "&ImageTypeLimit=1" + + "&Fields=" + detailsString + + "&format=json") + + log.debug("showParentContent Content Url : " + str(contentUrl)) + getContent(contentUrl, params) + +def checkService(): + home_window = HomeWindow() + timeStamp = home_window.getProperty("Service_Timestamp") + loops = 0 + while (timeStamp == "" and not xbmc.Monitor().abortRequested()): + timeStamp = home_window.getProperty("Service_Timestamp") + loops = loops + 1 + if (loops == 40): + log.error("EmbyCon Service Not Running, no time stamp, exiting") + xbmcgui.Dialog().ok(i18n('error'), i18n('service_not_running'), i18n('restart_kodi')) + sys.exit() + xbmc.sleep(200) + + log.debug("EmbyCon Service Timestamp: " + timeStamp) + log.debug("EmbyCon Current Timestamp: " + str(int(time.time()))) + + if ((int(timeStamp) + 240) < int(time.time())): + log.error("EmbyCon Service Not Running, time stamp to old, exiting") + xbmcgui.Dialog().ok(i18n('error'), i18n('service_not_running'), i18n('restart_kodi')) + sys.exit() + + +def search(handle, params): + log.debug('search Called: ' + str(params)) + item_type = params.get('item_type') + if not item_type: + return + kb = xbmc.Keyboard() + if item_type.lower() == 'movie': + heading_type = i18n('movies') + elif item_type.lower() == 'series': + heading_type = i18n('tvshows') + elif item_type.lower() == 'episode': + heading_type = i18n('episodes') + else: + heading_type = item_type + kb.setHeading(heading_type.capitalize() + ' ' + i18n('search').lower()) + kb.doModal() + if kb.isConfirmed(): + user_input = kb.getText().strip() + if user_input: + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) + user_input = urllib.quote(user_input) + xbmc.executebuiltin('Container.Update(plugin://plugin.video.embycon/?mode=SEARCH_RESULTS&query={user_input}&item_type={item_type}&index=0)' + .format(user_input=user_input, item_type=item_type)) # redirect for results to avoid page refreshing issues + else: + return + else: + return + + +def searchResults(params): + log.debug('searchResults Called: ' + str(params)) + + handle = int(sys.argv[1]) + query = params.get('query') + item_type = params.get('item_type') + if (not item_type) or (not query): + return + + limit = int(params.get('limit', 50)) + index = 0 + + settings = xbmcaddon.Addon(id='plugin.video.embycon') + server = downloadUtils.getServer() + userid = downloadUtils.getUserId() + details_string = getDetailsString() + + content_url = ('{server}/emby/Search/Hints?searchTerm=' + query + + '&IncludeItemTypes=' + item_type + + '&UserId={userid}' + '&StartIndex=' + str(index) + + '&Limit=' + str(limit) + + '&IncludePeople=false&IncludeMedia=true&IncludeGenres=false&IncludeStudios=false&IncludeArtists=false') + + if item_type.lower() == 'movie': + xbmcplugin.setContent(handle, 'movies') + view_type = 'Movies' + media_type = 'movie' + elif item_type.lower() == 'series': + xbmcplugin.setContent(handle, 'tvshows') + view_type = 'Series' + media_type = 'tvshow' + elif item_type.lower() == 'episode': + xbmcplugin.setContent(handle, 'episodes') + view_type = 'Episodes' + media_type = 'episode' + else: + xbmcplugin.setContent(handle, 'videos') + view_type = '' + media_type = 'video' + + setSort(handle, view_type) + + # show a progress indicator if needed + progress = None + if (settings.getSetting('showLoadProgress') == "true"): + progress = xbmcgui.DialogProgress() + progress.create(i18n('loading_content')) + progress.update(0, i18n('retrieving_data')) + + result = dataManager.GetContent(content_url) + log.debug('SearchHints jsonData: ' + str(result)) + + results = result.get('SearchHints') + if results is None: + results = [] + + item_count = 1 + total_results = int(result.get('TotalRecordCount', 0)) + log.debug('SEARCH_TOTAL_RESULTS: ' + str(total_results)) + list_items = [] + + for item in results: + item_id = item.get('ItemId') + name = title = item.get('Name') + log.debug('SEARCH_RESULT_NAME: ' + name) + + if progress is not None: + percent_complete = (float(item_count) / float(total_results)) * 100 + progress.update(int(percent_complete), i18n('processing_item:') + str(item_count)) + + tvshowtitle = '' + season = episode = None + + if (item.get('Type') == 'Episode') and (item.get('Series') is not None): + episode = '0' + if item.get('IndexNumber') is not None: + ep_number = item.get('IndexNumber') + if ep_number < 10: + episode = '0' + str(ep_number) + else: + episode = str(ep_number) + + season = '0' + season_number = item.get('ParentIndexNumber') + if season_number < 10: + season = '0' + str(season_number) + else: + season = str(season_number) + + tvshowtitle = item.get('Series') + title = tvshowtitle + ' - ' + title + + primary_image = thumb_image = backdrop_image = '' + primary_tag = item.get('PrimaryImageTag') + if primary_tag: + primary_image = downloadUtils.imageUrl(item_id, 'Primary', 0, 0, 0, imageTag=primary_tag, server=server) + thumb_id = item.get('ThumbImageId') + thumb_tag = item.get('ThumbImageTag') + if thumb_tag and thumb_id: + thumb_image = downloadUtils.imageUrl(thumb_id, 'Thumb', 0, 0, 0, imageTag=thumb_tag, server=server) + backdrop_id = item.get('BackdropImageItemId') + backdrop_tag = item.get('BackdropImageTag') + if backdrop_tag and backdrop_id: + backdrop_image = downloadUtils.imageUrl(backdrop_id, 'Backdrop', 0, 0, 0, imageTag=backdrop_tag, server=server) + + art = { + 'thumb': thumb_image or primary_image, + 'fanart': backdrop_image, + 'poster': primary_image or thumb_image, + 'banner': '', + 'clearlogo': '', + 'clearart': '', + 'discart': '', + 'landscape': backdrop_image, + 'tvshow.poster': primary_image + } + + if kodi_version > 17: + list_item = xbmcgui.ListItem(label=name, iconImage=art['thumb'], offscreen=True) + else: + list_item = xbmcgui.ListItem(label=name, iconImage=art['thumb']) + + info = {'title': title, 'tvshowtitle': tvshowtitle, 'mediatype': media_type} + log.debug('SEARCH_RESULT_ART: ' + str(art)) + list_item.setProperty('fanart_image', art['fanart']) + list_item.setProperty('discart', art['discart']) + list_item.setArt(art) + + # add count + list_item.setProperty('item_index', str(item_count)) + item_count += 1 + + if item.get('MediaType') == 'Video': + total_time = str(int(float(item.get('RunTimeTicks', '0')) / (10000000 * 60))) + list_item.setProperty('TotalTime', str(total_time)) + list_item.setProperty('IsPlayable', 'true') + list_item_url = 'plugin://plugin.video.embycon/?item_id=' + item_id + '&mode=PLAY' + is_folder = False + else: + item_url = ('{server}/emby/Users/{userid}' + + '/items?ParentId=' + item_id + + '&IsVirtualUnAired=false&IsMissing=false' + + '&Fields=' + details_string + + '&format=json') + list_item_url = 'plugin://plugin.video.embycon/?mode=GET_CONTENT&media_type={item_type}&url={item_url}'\ + .format(item_type=item_type, item_url=urllib.quote(item_url)) + list_item.setProperty('IsPlayable', 'false') + is_folder = True + + menu_items = addContextMenu({}, {'id': item_id}, is_folder) + if len(menu_items) > 0: + list_item.addContextMenuItems(menu_items, True) + + if (season is not None) and (episode is not None): + info['episode'] = episode + info['season'] = season + + info['year'] = item.get('ProductionYear', '') + + log.debug('SEARCH_RESULT_INFO: ' + str(info)) + list_item.setInfo('Video', infoLabels=info) + + item_tuple = (list_item_url, list_item, is_folder) + list_items.append(item_tuple) + + xbmcplugin.addDirectoryItems(handle, list_items) + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) + + if progress is not None: + progress.update(100, i18n('done')) + progress.close() + + +def PLAY(params, handle): + log.debug("== ENTER: PLAY ==") + + log.debug("PLAY ACTION PARAMS: " + str(params)) + item_id = params.get("item_id") + + auto_resume = int(params.get("auto_resume", "-1")) + log.debug("AUTO_RESUME: " + str(auto_resume)) + + forceTranscode = params.get("force_transcode", None) is not None + log.debug("FORCE_TRANSCODE: " + str(forceTranscode)) + + # set the current playing item id + # set all the playback info, this will be picked up by the service + # the service will then start the playback + + play_info = {} + play_info["item_id"] = item_id + play_info["auto_resume"] = str(auto_resume) + play_info["force_transcode"] = forceTranscode + play_data = json.dumps(play_info) + + home_window = HomeWindow() + home_window.setProperty("item_id", item_id) + home_window.setProperty("play_item_message", play_data) diff --git a/plugin.video.embycon/resources/lib/json_rpc.py b/plugin.video.embycon/resources/lib/json_rpc.py new file mode 100644 index 0000000..ae85a40 --- /dev/null +++ b/plugin.video.embycon/resources/lib/json_rpc.py @@ -0,0 +1,32 @@ +import json +import xbmc + +class json_rpc(object): + + id_ = 1 + jsonrpc = "2.0" + + def __init__(self, method, **kwargs): + + self.method = method + + for arg in kwargs: # id_(int), jsonrpc(str) + self.arg = arg + + def _query(self): + + query = { + + 'jsonrpc': self.jsonrpc, + 'id': self.id_, + 'method': self.method, + } + if self.params is not None: + query['params'] = self.params + + return json.dumps(query) + + def execute(self, params=None): + + self.params = params + return json.loads(xbmc.executeJSONRPC(self._query()))
\ No newline at end of file diff --git a/plugin.video.embycon/resources/lib/kodi_utils.py b/plugin.video.embycon/resources/lib/kodi_utils.py new file mode 100644 index 0000000..0bf3ab5 --- /dev/null +++ b/plugin.video.embycon/resources/lib/kodi_utils.py @@ -0,0 +1,67 @@ +import xbmc +import xbmcgui +import xbmcplugin +import xbmcaddon + +import sys +import json + +from simple_logging import SimpleLogging + +log = SimpleLogging(__name__) +addon = xbmcaddon.Addon(id='plugin.video.embycon') + +class HomeWindow(): + """ + xbmcgui.Window(10000) with add-on id prefixed to keys + """ + + def __init__(self): + self.id_string = 'plugin.video.embycon-%s' + self.window = xbmcgui.Window(10000) + + def getProperty(self, key): + key = self.id_string % key + value = self.window.getProperty(key) + # log.debug('HomeWindow: getProperty |%s| -> |%s|' % (key, value)) + return value + + def setProperty(self, key, value): + key = self.id_string % key + # log.debug('HomeWindow: setProperty |%s| -> |%s|' % (key, value)) + self.window.setProperty(key, value) + + def clearProperty(self, key): + key = self.id_string % key + # log.debug('HomeWindow: clearProperty |%s|' % key) + self.window.clearProperty(key) + + +def addMenuDirectoryItem(label, path, folder=True, thumbnail=None): + li = xbmcgui.ListItem(label, path=path) + if thumbnail is None: + thumbnail = addon.getAddonInfo('icon') + artLinks = {} + artLinks["thumb"] = thumbnail + artLinks["icon"] = thumbnail + li.setArt(artLinks) + xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder) + + +def getKodiVersion(): + 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"))) + log.debug("Version : " + str(version) + " - " + str(versionData)) + except: + version = 0.0 + log.error("Version Error : RAW Version Data : " + str(result)) + + return version diff --git a/plugin.video.embycon/resources/lib/menu_functions.py b/plugin.video.embycon/resources/lib/menu_functions.py new file mode 100644 index 0000000..d18345c --- /dev/null +++ b/plugin.video.embycon/resources/lib/menu_functions.py @@ -0,0 +1,510 @@ +# Gnu General Public License - see LICENSE.TXT + +import sys +import json +import urllib + +import xbmcplugin +import xbmcaddon + +from downloadutils import DownloadUtils +from utils import getDetailsString +from kodi_utils import addMenuDirectoryItem +from simple_logging import SimpleLogging +from translation import i18n + +log = SimpleLogging(__name__) +downloadUtils = DownloadUtils() + +__addon__ = xbmcaddon.Addon(id='plugin.video.embycon') + + +def showGenreList(): + log.debug("== ENTER: showGenreList() ==") + + server = downloadUtils.getServer() + if server is None: + return + + detailsString = getDetailsString() + + try: + jsonData = downloadUtils.downloadUrl("{server}/emby/Genres?SortBy=SortName&SortOrder=Ascending&IncludeTypes=Movie&Recursive=true&UserId={userid}&format=json") + log.debug("GENRE_LIST_DATA : " + jsonData) + except Exception, msg: + error = "Get connect : " + str(msg) + log.error(error) + + result = json.loads(jsonData) + result = result.get("Items") + + collections = [] + + for genre in result: + item_data = {} + item_data['title'] = genre.get("Name") + item_data['media_type'] = "Movies" + item_data['thumbnail'] = downloadUtils.getArtwork(genre, "Thumb", server=server) + item_data['path'] = ('{server}/emby/Users/{userid}/Items?Fields=' + detailsString + + '&Recursive=true&GenreIds=' + genre.get("Id") + + '&IncludeItemTypes=Movie' + + '&ImageTypeLimit=1&format=json') + collections.append(item_data) + + for collection in collections: + url = sys.argv[0] + ("?url=" + urllib.quote(collection['path']) + + "&mode=GET_CONTENT" + + "&media_type=" + collection["media_type"]) + log.debug("addMenuDirectoryItem: " + collection.get('title', i18n('unknown')) + " " + str(url)) + addMenuDirectoryItem(collection.get('title', i18n('unknown')), url, thumbnail=collection.get("thumbnail")) + + xbmcplugin.endOfDirectory(int(sys.argv[1])) + + +def showMovieAlphaList(): + log.debug("== ENTER: showMovieAlphaList() ==") + + settings = xbmcaddon.Addon(id='plugin.video.embycon') + server = downloadUtils.getServer() + if server is None: + return + detailsString = getDetailsString() + + collections = [] + + item_data = {} + item_data['title'] = "#" + item_data['media_type'] = "Movies" + item_data['path'] = ('{server}/emby/Users/{userid}' + + '/Items?Fields=' + detailsString + + '&Recursive=true' + + '&NameLessThan=A' + + '&IncludeItemTypes=Movie' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + alphaList = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "Y", "Z"] + + for alphaName in alphaList: + item_data = {} + item_data['title'] = alphaName + item_data['media_type'] = "Movies" + item_data['path'] = ('{server}/emby/Users/{userid}' + + '/Items?Fields=' + detailsString + + '&Recursive=true' + + '&NameStartsWith=' + alphaName + + '&IncludeItemTypes=Movie' + + '&ImageTypeLimit=1&format=json') + collections.append(item_data) + + for collection in collections: + url = (sys.argv[0] + "?url=" + urllib.quote(collection['path']) + + "&mode=GET_CONTENT&media_type=" + collection["media_type"]) + log.debug("addMenuDirectoryItem: " + collection.get('title', i18n('unknown')) + " " + str(url)) + addMenuDirectoryItem(collection.get('title', i18n('unknown')), url) + + xbmcplugin.endOfDirectory(int(sys.argv[1])) + + +def displaySections(): + log.debug("== ENTER: displaySections() ==") + xbmcplugin.setContent(int(sys.argv[1]), 'files') + + server = downloadUtils.getServer() + if server is None: + return + + # Add collections + detailsString = getDetailsString() + collections = getCollections(detailsString) + + if collections: + for collection in collections: + url = (sys.argv[0] + "?url=" + urllib.quote(collection['path']) + + "&mode=GET_CONTENT&media_type=" + collection["media_type"]) + if collection.get("name_format") is not None: + url += "&name_format=" + urllib.quote(collection.get("name_format")) + log.debug("addMenuDirectoryItem: " + collection.get('title', i18n('unknown')) + " " + str(url)) + addMenuDirectoryItem(collection.get('title', i18n('unknown')), url, thumbnail=collection.get("thumbnail")) + + addMenuDirectoryItem(i18n('movies_genre'), "plugin://plugin.video.embycon/?mode=MOVIE_GENRA") + addMenuDirectoryItem(i18n('movies_az'), "plugin://plugin.video.embycon/?mode=MOVIE_ALPHA") + addMenuDirectoryItem(i18n('search'), "plugin://plugin.video.embycon/?mode=SEARCH") + + addMenuDirectoryItem(i18n('show_clients'), "plugin://plugin.video.embycon/?mode=SHOW_SERVER_SESSIONS") + addMenuDirectoryItem(i18n('change_user'), "plugin://plugin.video.embycon/?mode=CHANGE_USER") + + addMenuDirectoryItem(i18n('detect_server'), "plugin://plugin.video.embycon/?mode=DETECT_SERVER_USER") + addMenuDirectoryItem(i18n('show_settings'), "plugin://plugin.video.embycon/?mode=SHOW_SETTINGS") + + if collections: + addMenuDirectoryItem(i18n('widgets'), "plugin://plugin.video.embycon/?mode=WIDGETS") + + xbmcplugin.endOfDirectory(int(sys.argv[1])) + + +def getCollections(detailsString): + log.debug("== ENTER: getCollections ==") + + server = downloadUtils.getServer() + if server is None: + return [] + + userid = downloadUtils.getUserId() + + if userid == None or len(userid) == 0: + log.debug("No userid so returning []") + return [] + + try: + jsonData = downloadUtils.downloadUrl("{server}/emby/Users/{userid}/Items/Root?format=json") + except Exception, msg: + error = "Get connect : " + str(msg) + log.error(error) + return [] + + log.debug("jsonData : " + jsonData) + result = json.loads(jsonData) + if result is None: + return [] + + parentid = result.get("Id") + log.debug("parentid : " + parentid) + + htmlpath = "{server}/emby/Users/{userid}/items?ParentId=" + parentid + "&Sortby=SortName&format=json" + jsonData = downloadUtils.downloadUrl(htmlpath) + log.debug("jsonData : " + jsonData) + collections = [] + + result = [] + try: + result = json.loads(jsonData) + result = result.get("Items") + except Exception as error: + log.error("Error parsing user collection: " + str(error)) + + for item in result: + item_name = (item.get("Name")).encode('utf-8') + + collection_type = item.get('CollectionType', None) + log.debug("CollectionType: " + str(collection_type)) + log.debug("Title: " + item_name) + + if collection_type in ["tvshows", "movies", "boxsets"]: + collections.append({ + 'title': item_name, + 'thumbnail': downloadUtils.getArtwork(item, "Primary", server=server), + 'path': ('{server}/emby/Users/{userid}/Items' + + '?ParentId=' + item.get("Id") + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&Fields=' + detailsString + + '&ImageTypeLimit=1' + + '&format=json'), + 'media_type': collection_type}) + + if collection_type == "tvshows": + collections.append({ + 'title': item_name + i18n('_unwatched'), + 'thumbnail': downloadUtils.getArtwork(item, "Primary", server=server), + 'path': ('{server}/emby/Users/{userid}/Items' + + '?ParentId=' + item.get("Id") + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&Fields=' + detailsString + + '&Filters=IsUnplayed' + + '&Recursive=true' + + '&IncludeItemTypes=Series' + + '&ImageTypeLimit=1' + + '&format=json'), + 'media_type': 'tvshows'}) + collections.append({ + 'title': item_name + i18n('_in_progress'), + 'thumbnail': downloadUtils.getArtwork(item, "Primary", server=server), + 'path': ('{server}/emby/Users/{userid}/Items' + + '?ParentId=' + item.get("Id") + + '&Limit={ItemLimit}' + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&Fields=' + detailsString + + '&Filters=IsResumable' + + '&Recursive=true' + + '&IncludeItemTypes=Episode' + + '&ImageTypeLimit=1' + + '&format=json'), + 'media_type': 'Episodes', + 'name_format': 'episode_name_format'}) + collections.append({ + 'title': item_name + i18n('_recently_added'), + 'thumbnail': downloadUtils.getArtwork(item, "Primary", server=server), + 'path': ('{server}/emby/Users/{userid}/Items' + + '?ParentId=' + item.get("Id") + + '&Limit={ItemLimit}' + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&Fields=' + detailsString + + '&SortBy=DateCreated' + + '&SortOrder=Descending' + + '&Filters=IsUnplayed,IsNotFolder' + + '&Recursive=true' + + '&IncludeItemTypes=Episode' + + '&ImageTypeLimit=1' + + '&format=json'), + 'media_type': 'Episodes', + 'name_format': 'episode_name_format'}) + collections.append({ + 'title': item_name + i18n('_next_up'), + 'thumbnail': downloadUtils.getArtwork(item, "Primary", server=server), + 'path': ('{server}/emby/Shows/NextUp/?Userid={userid}' + + '&ParentId=' + item.get("Id") + + '&Limit={ItemLimit}' + + '&Recursive=true' + + '&Fields=' + detailsString + + '&Filters=IsUnplayed,IsNotFolder' + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&IncludeItemTypes=Episode' + + '&ImageTypeLimit=1' + + '&format=json'), + 'media_type': 'Episodes', + 'name_format': 'episode_name_format'}) + + if collection_type == "movies": + collections.append({ + 'title': item_name + i18n('_unwatched'), + 'thumbnail': downloadUtils.getArtwork(item, "Primary", server=server), + 'path': ('{server}/emby/Users/{userid}/Items' + + '?ParentId=' + item.get("Id") + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&Fields=' + detailsString + + '&Filters=IsUnplayed' + + '&ImageTypeLimit=1' + + '&format=json'), + 'media_type': collection_type}) + collections.append({ + 'title': item_name + i18n('_in_progress'), + 'thumbnail': downloadUtils.getArtwork(item, "Primary", server=server), + 'path': ('{server}/emby/Users/{userid}/Items' + + '?ParentId=' + item.get("Id") + + '&Limit={ItemLimit}' + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&Fields=' + detailsString + + '&Filters=IsResumable' + + '&ImageTypeLimit=1' + + '&format=json'), + 'media_type': collection_type}) + collections.append({ + 'title': item_name + i18n('_recently_added'), + 'thumbnail': downloadUtils.getArtwork(item, "Primary", server=server), + 'path': ('{server}/emby/Users/{userid}/Items' + + '?ParentId=' + item.get("Id") + + '&Limit={ItemLimit}' + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&Fields=' + detailsString + + '&SortBy=DateCreated' + + '&SortOrder=Descending' + + '&Filters=IsUnplayed,IsNotFolder' + + '&ImageTypeLimit=1' + + '&format=json'), + 'media_type': collection_type}) + + # Add standard nodes + item_data = {} + item_data['title'] = i18n('movies_all') + item_data['media_type'] = 'Movies' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Fields=' + detailsString + + '&Recursive=true' + + '&IncludeItemTypes=Movie' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('movies_unwatched') + item_data['media_type'] = 'Movies' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Recursive=true' + + '&Fields=' + detailsString + + '&Filters=IsUnplayed' + + '&IncludeItemTypes=Movie' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('movies_in_progress') + item_data['media_type'] = 'Movies' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Limit={ItemLimit}' + + '&Recursive=true' + + '&Fields=' + detailsString + + '&Filters=IsResumable' + + '&IncludeItemTypes=Movie' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('movies_recently_added') + item_data['media_type'] = 'Movies' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Limit={ItemLimit}' + + '&Recursive=true' + + '&SortBy=DateCreated' + + '&Fields=' + detailsString + + '&SortOrder=Descending' + + '&Filters=IsUnplayed,IsNotFolder' + + '&IncludeItemTypes=Movie' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('movies_favorites') + item_data['media_type'] = 'Movies' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Fields=' + detailsString + + '&Recursive=true' + + '&Filters=IsFavorite' + + '&IncludeItemTypes=Movie' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('movies_boxsets') + item_data['media_type'] = 'BoxSets' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Recursive=true' + + '&Fields=' + detailsString + + '&IncludeItemTypes=BoxSet' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('tvshows_all') + item_data['media_type'] = 'tvshows' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Fields=' + detailsString + + '&Recursive=true' + + '&IncludeItemTypes=Series' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('tvshows_unwatched') + item_data['media_type'] = 'tvshows' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Fields=' + detailsString + + '&Recursive=true' + + '&Filters=IsUnplayed' + + '&IncludeItemTypes=Series' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('tvshows_favorites') + item_data['media_type'] = 'tvshows' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Fields=' + detailsString + + '&Recursive=true' + + '&Filters=IsFavorite' + + '&IncludeItemTypes=Series' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('episodes_in_progress') + item_data['media_type'] = 'Episodes' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Limit={ItemLimit}' + + '&Recursive=true' + + '&Fields=' + detailsString + + '&Filters=IsResumable' + + '&IncludeItemTypes=Episode' + + '&ImageTypeLimit=1' + + '&format=json') + item_data['name_format'] = 'episode_name_format' + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('episodes_recently_added') + item_data['media_type'] = 'Episodes' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Limit={ItemLimit}' + + '&Recursive=true' + + '&SortBy=DateCreated' + + '&Fields=' + detailsString + + '&SortOrder=Descending' + + '&Filters=IsUnplayed,IsNotFolder' + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&IncludeItemTypes=Episode' + + '&ImageTypeLimit=1' + + '&format=json') + item_data['name_format'] = 'episode_name_format' + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('episodes_up_next') + item_data['media_type'] = 'Episodes' + item_data['path'] = ('{server}/emby/Shows/NextUp/?Userid={userid}' + + '&Limit={ItemLimit}' + + '&Recursive=true' + + '&Fields=' + detailsString + + '&Filters=IsUnplayed,IsNotFolder' + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&IncludeItemTypes=Episode' + + '&ImageTypeLimit=1' + + '&format=json') + item_data['name_format'] = 'episode_name_format' + collections.append(item_data) + + item_data = {} + item_data['title'] = i18n('upcoming_tv') + item_data['media_type'] = 'Episodes' + item_data['path'] = ('{server}/emby/Users/{userid}/Items' + + '?Recursive=true' + + '&SortBy=PremiereDate' + + '&Fields=' + detailsString + + '&SortOrder=Ascending' + + '&IsVirtualUnaired=true' + + '&IsNotFolder' + + '&IncludeItemTypes=Episode' + + '&ImageTypeLimit=1' + + '&format=json') + collections.append(item_data) + + return collections + + +def showWidgets(): + addMenuDirectoryItem(i18n('emby_movies'), 'plugin://plugin.video.embycon/?mode=SHOW_CONTENT&item_type=Movie&media_type=Movies') + addMenuDirectoryItem(i18n('emby_tvshows'), 'plugin://plugin.video.embycon/?mode=SHOW_CONTENT&item_type=Series&media_type=TVShows') + + addMenuDirectoryItem(i18n('movies_recently_added'), 'plugin://plugin.video.embycon/?mode=WIDGET_CONTENT&type=recent_movies') + addMenuDirectoryItem(i18n('movies_in_progress'), 'plugin://plugin.video.embycon/?mode=WIDGET_CONTENT&type=inprogress_movies') + addMenuDirectoryItem(i18n('movies_random'), 'plugin://plugin.video.embycon/?mode=WIDGET_CONTENT&type=random_movies') + addMenuDirectoryItem(i18n('episodes_recently_added'), 'plugin://plugin.video.embycon/?mode=WIDGET_CONTENT&type=recent_episodes') + addMenuDirectoryItem(i18n('episodes_in_progress'), 'plugin://plugin.video.embycon/?mode=WIDGET_CONTENT&type=inprogress_episodes') + addMenuDirectoryItem(i18n('episodes_up_next'), 'plugin://plugin.video.embycon/?mode=WIDGET_CONTENT&type=nextup_episodes') + + xbmcplugin.endOfDirectory(int(sys.argv[1])) + + +def showSearch(): + addMenuDirectoryItem(i18n('movies'), 'plugin://plugin.video.embycon/?mode=NEW_SEARCH&item_type=Movie') + addMenuDirectoryItem(i18n('tvshows'), 'plugin://plugin.video.embycon/?mode=NEW_SEARCH&item_type=Series') + addMenuDirectoryItem(i18n('episodes'), 'plugin://plugin.video.embycon/?mode=NEW_SEARCH&item_type=Episode') + + xbmcplugin.endOfDirectory(int(sys.argv[1])) diff --git a/plugin.video.embycon/resources/lib/play_utils.py b/plugin.video.embycon/resources/lib/play_utils.py new file mode 100644 index 0000000..f7cba7c --- /dev/null +++ b/plugin.video.embycon/resources/lib/play_utils.py @@ -0,0 +1,173 @@ +# Gnu General Public License - see LICENSE.TXT + +import xbmc +import xbmcgui +import xbmcaddon + +from datetime import timedelta +import time +import json + +from simple_logging import SimpleLogging +from downloadutils import DownloadUtils +from resume_dialog import ResumeDialog +from utils import PlayUtils, getArt +from kodi_utils import HomeWindow +from translation import i18n +from json_rpc import json_rpc + +log = SimpleLogging(__name__) +downloadUtils = DownloadUtils() + + +def playFile(play_info): + + id = play_info.get("item_id") + auto_resume = play_info.get("auto_resume") + force_transcode = play_info.get("force_transcode") + + log.debug("playFile id(%s) resume(%s) force_transcode(%s)" % (id, auto_resume, force_transcode)) + + settings = xbmcaddon.Addon('plugin.video.embycon') + addon_path = settings.getAddonInfo('path') + playback_type = settings.getSetting("playback_type") + jump_back_amount = int(settings.getSetting("jump_back_amount")) + + server = downloadUtils.getServer() + + jsonData = downloadUtils.downloadUrl("{server}/emby/Users/{userid}/Items/" + id + "?format=json", + suppress=False, popup=1) + result = json.loads(jsonData) + + seekTime = 0 + auto_resume = int(auto_resume) + + if auto_resume != -1: + seekTime = (auto_resume / 1000) / 10000 + else: + userData = result.get("UserData") + if userData.get("PlaybackPositionTicks") != 0: + + reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000 + seekTime = reasonableTicks / 10000 + displayTime = str(timedelta(seconds=seekTime)) + + resumeDialog = ResumeDialog("ResumeDialog.xml", addon_path, "default", "720p") + resumeDialog.setResumeTime("Resume from " + displayTime) + resumeDialog.doModal() + resume_result = resumeDialog.getResumeAction() + del resumeDialog + log.debug("Resume Dialog Result: " + str(resume_result)) + + # check system settings for play action + # if prompt is set ask to set it to auto resume + params = {"setting": "myvideos.selectaction"} + setting_result = json_rpc('Settings.getSettingValue').execute(params) + log.debug("Current Setting (myvideos.selectaction): %s" % setting_result) + current_value = setting_result.get("result", None) + if current_value is not None: + current_value = current_value.get("value", -1) + if current_value not in (2,3): + return_value = xbmcgui.Dialog().yesno(i18n('extra_prompt'), i18n('turn_on_auto_resume?')) + if return_value: + params = {"setting": "myvideos.selectaction", "value": 2} + json_rpc_result = json_rpc('Settings.setSettingValue').execute(params) + log.debug("Save Setting (myvideos.selectaction): %s" % json_rpc_result) + + if resume_result == 1: + seekTime = 0 + elif resume_result == -1: + return + + listitem_props = [] + playurl = None + + # check if strm file, path will contain contain strm contents + if result.get('MediaSources'): + source = result['MediaSources'][0] + if source.get('Container') == 'strm': + playurl, listitem_props = PlayUtils().getStrmDetails(result) + + if not playurl: + playurl = PlayUtils().getPlayUrl(id, result, force_transcode) + + log.debug("Play URL: " + playurl + " ListItem Properties: " + str(listitem_props)) + + playback_type_string = "DirectPlay" + if playback_type == "2" or force_transcode: + playback_type_string = "Transcode" + elif playback_type == "1": + playback_type_string = "DirectStream" + + home_window = HomeWindow() + home_window.setProperty("PlaybackType_" + id, playback_type_string) + + # add the playback type into the overview + if result.get("Overview", None) is not None: + result["Overview"] = playback_type_string + "\n" + result.get("Overview") + else: + result["Overview"] = playback_type_string + + list_item = xbmcgui.ListItem(label=result.get("Name", i18n('missing_title')), path=playurl) + + list_item = setListItemProps(id, list_item, result, server, listitem_props) + + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + playlist.clear() + playlist.add(playurl, list_item) + xbmc.Player().play(playlist) + + if seekTime == 0: + return + + count = 0 + while not xbmc.Player().isPlaying(): + log.debug("Not playing yet...sleep for 1 sec") + count = count + 1 + if count >= 10: + return + else: + xbmc.Monitor().waitForAbort(1) + + seekTime = seekTime - jump_back_amount + + while xbmc.Player().getTime() < (seekTime - 5): + # xbmc.Player().pause() + xbmc.sleep(100) + xbmc.Player().seekTime(seekTime) + xbmc.sleep(100) + # xbmc.Player().play() + +def setListItemProps(id, listItem, result, server, extra_props): + # set up item and item info + thumbID = id + eppNum = -1 + seasonNum = -1 + + art = getArt(result, server=server) + listItem.setIconImage(art['thumb']) # back compat + listItem.setProperty('fanart_image', art['fanart']) # back compat + listItem.setProperty('discart', art['discart']) # not avail to setArt + listItem.setArt(art) + + listItem.setProperty('IsPlayable', 'true') + listItem.setProperty('IsFolder', 'false') + + for prop in extra_props: + listItem.setProperty(prop[0], prop[1]) + + # play info + details = { + 'title': result.get("Name", i18n('missing_title')), + 'plot': result.get("Overview") + } + + if (eppNum > -1): + details["episode"] = str(eppNum) + + if (seasonNum > -1): + details["season"] = str(seasonNum) + + listItem.setInfo("Video", infoLabels=details) + + return listItem diff --git a/plugin.video.embycon/resources/lib/resume_dialog.py b/plugin.video.embycon/resources/lib/resume_dialog.py new file mode 100644 index 0000000..5b73f54 --- /dev/null +++ b/plugin.video.embycon/resources/lib/resume_dialog.py @@ -0,0 +1,43 @@ +# Gnu General Public License - see LICENSE.TXT + +import xbmcgui + +from simple_logging import SimpleLogging +from translation import i18n + +log = SimpleLogging(__name__) + + +class ResumeDialog(xbmcgui.WindowXMLDialog): + resumePlay = -1 + resumeTimeStamp = "" + + def __init__(self, *args, **kwargs): + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + log.debug("ResumeDialog INITIALISED") + + def onInit(self): + self.action_exitkeys_id = [10, 13] + self.getControl(3010).setLabel(self.resumeTimeStamp) + self.getControl(3011).setLabel(i18n('start_from_beginning')) + + def onFocus(self, controlId): + pass + + def doAction(self, actionID): + pass + + def onClick(self, controlID): + + if (controlID == 3010): + self.resumePlay = 0 + self.close() + if (controlID == 3011): + self.resumePlay = 1 + self.close() + + def setResumeTime(self, timeStamp): + self.resumeTimeStamp = timeStamp + + def getResumeAction(self): + return self.resumePlay diff --git a/plugin.video.embycon/resources/lib/server_detect.py b/plugin.video.embycon/resources/lib/server_detect.py new file mode 100644 index 0000000..392a9df --- /dev/null +++ b/plugin.video.embycon/resources/lib/server_detect.py @@ -0,0 +1,185 @@ +# Gnu General Public License - see LICENSE.TXT + +import socket +import json +from urlparse import urlparse + +import xbmcaddon +import xbmcgui +import xbmc + +from kodi_utils import HomeWindow +from downloadutils import DownloadUtils +from simple_logging import SimpleLogging +from translation import i18n + +log = SimpleLogging(__name__) + +__addon__ = xbmcaddon.Addon(id='plugin.video.embycon') +__addon_name__ = __addon__.getAddonInfo('name') +downloadUtils = DownloadUtils() + + +def getServerDetails(): + log.debug("Getting Server Details from Network") + + MESSAGE = "who is EmbyServer?" + MULTI_GROUP = ("<broadcast>", 7359) + #MULTI_GROUP = ("127.0.0.1", 7359) + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(4.0) + + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 4) # timeout + 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) + + log.debug("MutliGroup : " + str(MULTI_GROUP)) + log.debug("Sending UDP Data : " + MESSAGE) + sock.sendto(MESSAGE, MULTI_GROUP) + + servers = [] + + # while True: + try: + data, addr = sock.recvfrom(1024) # buffer size + servers.append(json.loads(data)) + except Exception as e: + log.error("Read UPD responce: %s" % e) + # break + + log.debug("Found Servers: %s" % servers) + return servers + + +def checkServer(force=False, change_user=False, notify=False): + log.debug("checkServer Called") + + settings = xbmcaddon.Addon(id='plugin.video.embycon') + serverUrl = "" + + if force is False: + # if not forcing use server details from settings + svr = downloadUtils.getServer() + if svr is not None: + serverUrl = svr + + # if the server is not set then try to detect it + if serverUrl == "": + serverInfo = getServerDetails() + + serverNames = [] + for server in serverInfo: + serverNames.append(server.get("Name", i18n('n/a'))) + if serverNames: + return_index = xbmcgui.Dialog().select(i18n('select_server'), serverNames) + else: + xbmcgui.Dialog().ok(__addon_name__, i18n('no_server_detected')) + return_index = -1 + + if (return_index == -1): + xbmc.executebuiltin("ActivateWindow(Home)") + return + + serverUrl = serverInfo[return_index]["Address"] + log.debug("Selected server: " + serverUrl) + + # parse the url + url_bits = urlparse(serverUrl) + server_address = url_bits.hostname + server_port = str(url_bits.port) + server_protocol = url_bits.scheme + log.debug("Detected server info " + server_protocol + " - " + server_address + " - " + server_port) + + # save the server info + settings.setSetting("port", server_port) + settings.setSetting("ipaddress", server_address) + + if server_protocol == "https": + settings.setSetting("use_https", "true") + else: + settings.setSetting("use_https", "false") + + if notify: + xbmcgui.Dialog().ok(i18n('server_detect_succeeded'), i18n('found_server'), + i18n('address:') + server_address, i18n('server_port:') + server_port) + + # we need to change the user + current_username = settings.getSetting("username") + + # if asked or we have no current user then show user selection screen + if change_user or len(current_username) == 0: + # get a list of users + log.debug("Getting user list") + jsonData = downloadUtils.downloadUrl(serverUrl + "/emby/Users/Public?format=json", authenticate=False) + + log.debug("jsonData : " + str(jsonData)) + result = json.loads(jsonData) + if result is None: + result = [] + + names = [] + user_list = [] + secured = [] + for user in result: + config = user.get("Configuration") + if (config != None): + if (config.get("IsHidden") is None) or (config.get("IsHidden") is False): + name = user.get("Name") + user_list.append(name) + if (user.get("HasPassword") is True): + secured.append(True) + name = i18n('username_secured') % name + else: + secured.append(False) + names.append(name) + + if (len(current_username) > 0) and (not any(n == current_username for n in user_list)): + names.insert(0, i18n('username_userdefined') % current_username) + user_list.insert(0, current_username) + secured.insert(0, True) + + names.insert(0, i18n('username_userinput')) + user_list.insert(0, '') + secured.insert(0, True) + log.debug("User List : " + str(names)) + log.debug("User List : " + str(user_list)) + + return_value = xbmcgui.Dialog().select(i18n('select_user'), names) + + if (return_value > -1): + log.debug("Selected User Index : " + str(return_value)) + if return_value == 0: + kb = xbmc.Keyboard() + kb.setHeading(i18n('username:')) + kb.doModal() + if kb.isConfirmed(): + selected_user = kb.getText() + else: + selected_user = None + else: + selected_user = user_list[return_value] + + log.debug("Selected User Name : " + str(selected_user)) + + if selected_user: + # we have a user so save it + log.debug("Saving Username : " + selected_user) + settings.setSetting("username", selected_user) + if secured[return_value] is True: + kb = xbmc.Keyboard() + kb.setHeading(i18n('password:')) + kb.setHiddenInput(True) + kb.doModal() + if kb.isConfirmed(): + log.debug("Saving Password for Username : " + selected_user) + settings.setSetting('password', kb.getText()) + else: + settings.setSetting('password', '') + + home_window = HomeWindow() + home_window.clearProperty("userid") + home_window.clearProperty("AccessToken") + + xbmc.executebuiltin("ActivateWindow(Home)") diff --git a/plugin.video.embycon/resources/lib/server_sessions.py b/plugin.video.embycon/resources/lib/server_sessions.py new file mode 100644 index 0000000..85e7a9d --- /dev/null +++ b/plugin.video.embycon/resources/lib/server_sessions.py @@ -0,0 +1,65 @@ + +import json +import sys +import xbmcgui +import xbmcplugin + +from downloadutils import DownloadUtils +from simple_logging import SimpleLogging + +log = SimpleLogging(__name__) + +def showServerSessions(): + log.debug("showServerSessions Called") + + handle = int(sys.argv[1]) + downloadUtils = DownloadUtils() + url = "{server}/emby/Sessions" + result_data = downloadUtils.downloadUrl(url) + results = json.loads(result_data) + + if results is None: + return + + list_items = [] + for session in results: + device_name = session.get("DeviceName", "na") + user_name = session.get("UserName", "na") + client_name = session.get("Client", "na") + + session_info = device_name + " - " + user_name + " - " + client_name + + # playstate + percenatge_played = 0 + play_state = session.get("PlayState", None) + if play_state is not None: + runtime = 0 + media_id = play_state.get("MediaSourceId", None) + log.debug("Media ID " + str(media_id)) + if media_id is not None: + jsonData = downloadUtils.downloadUrl("{server}/emby/Users/{userid}/Items/" + + media_id + "?format=json", + suppress=False, popup=1) + media_info = json.loads(jsonData) + log.debug("Media Info " + str(media_info)) + runtime = media_info.get("RunTimeTicks", 0) + log.debug("Media Runtime " + str(runtime)) + + position_ticks = play_state.get("PositionTicks", 0) + log.debug("Media PositionTicks " + str(position_ticks)) + if position_ticks > 0 and runtime > 0: + percenatge_played = (position_ticks / float(runtime)) * 100.0 + percenatge_played = int(percenatge_played) + + now_playing = session.get("NowPlayingItem", None) + log.debug("NOW_PLAYING: " + str(now_playing)) + if now_playing is not None: + session_info += " (" + now_playing.get("Name", "na") + " " + str(percenatge_played) + "%)" + + log.debug(session_info) + list_item = xbmcgui.ListItem(label=session_info) + item_tuple = ("", list_item, False) + list_items.append(item_tuple) + + xbmcplugin.addDirectoryItems(handle, list_items) + xbmcplugin.endOfDirectory(handle, cacheToDisc=False) diff --git a/plugin.video.embycon/resources/lib/simple_logging.py b/plugin.video.embycon/resources/lib/simple_logging.py new file mode 100644 index 0000000..f25bcd4 --- /dev/null +++ b/plugin.video.embycon/resources/lib/simple_logging.py @@ -0,0 +1,39 @@ +# Gnu General Public License - see LICENSE.TXT + +import xbmc +import xbmcaddon +from json_rpc import json_rpc + +class SimpleLogging(): + name = "" + enable_logging = False + + def __init__(self, name): + settings = xbmcaddon.Addon(id='plugin.video.embycon') + prefix = settings.getAddonInfo('name') + self.name = prefix + '.' + name + params = {"setting": "debug.showloginfo"} + setting_result = json_rpc('Settings.getSettingValue').execute(params) + current_value = setting_result.get("result", None) + if current_value is not None: + self.enable_logging = current_value.get("value", False) + #xbmc.log("LOGGING_ENABLED %s: %s" % (self.name, str(self.enable_logging)), level=xbmc.LOGDEBUG) + + def __str__(self): + return "LoggingEnabled: " + str(self.enable_logging) + + def error(self, msg): + try: + xbmc.log(self.format(msg, "ERROR"), level=xbmc.LOGERROR) + except UnicodeEncodeError: + xbmc.log(self.format(msg, "ERROR").encode('utf-8'), level=xbmc.LOGERROR) + + def debug(self, msg): + if (self.enable_logging): + try: + xbmc.log(self.format(msg, "DEBUG"), level=xbmc.LOGDEBUG) + except UnicodeEncodeError: + xbmc.log(self.format(msg, "DEBUG").encode('utf-8'), level=xbmc.LOGDEBUG) + + def format(self, msg, levelValue): + return self.name + "(" + str(levelValue) + ") -> " + msg diff --git a/plugin.video.embycon/resources/lib/translation.py b/plugin.video.embycon/resources/lib/translation.py new file mode 100644 index 0000000..5b22f79 --- /dev/null +++ b/plugin.video.embycon/resources/lib/translation.py @@ -0,0 +1,98 @@ +import xbmcaddon +from simple_logging import SimpleLogging + +log = SimpleLogging(__name__) +addon = xbmcaddon.Addon(id='plugin.video.embycon') + + +def i18n(string_id): + try: + return addon.getLocalizedString(STRINGS[string_id]).encode('utf-8', 'ignore') + except Exception as e: + log.error('Failed String Lookup: %s (%s)' % (string_id, e)) + return string_id + + +STRINGS = { + 'server_port:': 30001, + 'username:': 30005, + 'password:': 30006, + 'incorrect_user_pass': 30044, + 'username_not_found': 30045, + 'deleting': 30052, + 'waiting_server_delete': 30053, + 'username_secured': 30060, + 'username_userdefined': 30061, + 'username_userinput': 30062, + 'n/a': 30063, + 'confirm_file_delete': 30091, + 'file_delete_confirm': 30092, + 'loading_content': 30112, + 'retrieving_data': 30113, + 'done': 30125, + 'processing_item:': 30126, + 'error': 30135, + 'service_not_running': 30136, + 'restart_kodi': 30137, + 'no_media_type': 30139, + 'select_server': 30166, + 'server_detect_succeeded': 30167, + 'found_server': 30168, + 'address:': 30169, + 'select_user': 30180, + 'url_error_': 30200, + 'unable_connect_server': 30201, + 'tvshows': 30229, + 'default_view': 30230, + 'movies': 30231, + 'boxsets': 30232, + 'series': 30233, + 'seasons': 30234, + 'episodes': 30235, + 'save': 30236, + 'start_from_beginning': 30237, + 'default_sort': 30238, + 'next_page': 30245, + 'search': 30246, + 'widgets': 30247, + 'emby_movies': 30248, + 'emby_tvshows': 30249, + 'unknown': 30250, + 'movies_genre': 30251, + 'movies_az': 30252, + 'change_user': 30253, + 'show_settings': 30254, + 'movies_all': 30256, + 'movies_recently_added': 30257, + 'movies_in_progress': 30258, + 'movies_favorites': 30259, + 'movies_boxsets': 30260, + 'tvshows_all': 30261, + 'tvshows_favorites': 30262, + 'episodes_recently_added': 30263, + 'episodes_in_progress': 30264, + 'episodes_up_next': 30265, + 'upcoming_tv': 30266, + '_in_progress': 30267, + '_recently_added': 30268, + 'movies_random': 30269, + 'emby_mark_watched': 30270, + 'emby_mark_unwatched': 30271, + 'emby_set_favorite': 30272, + 'emby_unset_favorite': 30273, + 'emby_delete': 30274, + 'missing_title': 30280, + 'emby_force_transcode': 30275, + 'extra_prompt': 30276, + 'turn_on_auto_resume?': 30277, + 'skin_not_supported': 30281, + 'no_server_detected': 30282, + 'play_next_title': 30283, + 'play_next_question': 30284, + '_next_up': 30278, + 'detect_server': 30011, + 'show_clients': 30017, + 'tvshows_unwatched': 30279, + '_unwatched': 30285, + 'movies_unwatched': 30286 +} diff --git a/plugin.video.embycon/resources/lib/utils.py b/plugin.video.embycon/resources/lib/utils.py new file mode 100644 index 0000000..6afa3bd --- /dev/null +++ b/plugin.video.embycon/resources/lib/utils.py @@ -0,0 +1,191 @@ +# Gnu General Public License - see LICENSE.TXT +import xbmcaddon + +import re + +from downloadutils import DownloadUtils +from simple_logging import SimpleLogging +from clientinfo import ClientInformation + +# define our global download utils +downloadUtils = DownloadUtils() +log = SimpleLogging(__name__) + + +########################################################################### +class PlayUtils(): + def getPlayUrl(self, id, result, force_transcode): + log.debug("getPlayUrl") + addonSettings = xbmcaddon.Addon(id='plugin.video.embycon') + playback_type = addonSettings.getSetting("playback_type") + server = downloadUtils.getServer() + log.debug("playback_type: " + playback_type) + if force_transcode: + log.debug("playback_type: FORCED_TRANSCODE") + playurl = None + + # transcode + if playback_type == "2" or force_transcode: + + playback_bitrate = addonSettings.getSetting("playback_bitrate") + log.debug("playback_bitrate: " + playback_bitrate) + + width_options = ["640", "720", "1024", "1280", "1440", "1600", "1920", "2600", "4096"] + playback_max_width = width_options[int(addonSettings.getSetting("playback_max_width"))] + playback_video_force_8 = addonSettings.getSetting("playback_video_force_8") == "true" + + clientInfo = ClientInformation() + deviceId = clientInfo.getDeviceId() + bitrate = int(playback_bitrate) * 1000 + user_token = downloadUtils.authenticate() + + playurl = ( + "%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s" + % (server, id, id, deviceId, bitrate)) + + playurl = playurl + "&maxWidth=" + playback_max_width + + if playback_video_force_8: + playurl = playurl + "&MaxVideoBitDepth=8" + + playurl = playurl + "&api_key=" + user_token + + # do direct path playback + elif playback_type == "0": + playurl = result.get("Path") + + # handle DVD structure + if (result.get("VideoType") == "Dvd"): + playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO" + elif (result.get("VideoType") == "BluRay"): + playurl = playurl + "/BDMV/index.bdmv" + + smb_username = addonSettings.getSetting('smbusername') + smb_password = addonSettings.getSetting('smbpassword') + + # add smb creds + if smb_username == '': + playurl = playurl.replace("\\\\", "smb://") + else: + playurl = playurl.replace("\\\\", "smb://" + smb_username + ':' + smb_password + '@') + + playurl = playurl.replace("\\", "/") + + # do direct http streaming playback + elif playback_type == "1": + playurl = "%s/emby/Videos/%s/stream?static=true" % (server, id) + user_token = downloadUtils.authenticate() + playurl = playurl + "&api_key=" + user_token + + log.debug("Playback URL: " + playurl) + return playurl.encode('utf-8') + + def getStrmDetails(self, result): + playurl = None + listitem_props = [] + + source = result['MediaSources'][0] + contents = source.get('Path').encode('utf-8') # contains contents of strm file with linebreaks + + line_break = '\r' + if '\r\n' in contents: + line_break += '\n' + + lines = contents.split(line_break) + for line in lines: + line = line.strip() + if line.startswith('#KODIPROP:'): + match = re.search('#KODIPROP:(?P<item_property>[^=]+?)=(?P<property_value>.+)', line) + if match: + listitem_props.append((match.group('item_property'), match.group('property_value'))) + elif line != '': + playurl = line + + log.debug("Playback URL: " + playurl + " ListItem Properties: " + str(listitem_props)) + return playurl, listitem_props + + +def getDetailsString(): + + addonSettings = xbmcaddon.Addon(id='plugin.video.embycon') + include_media = addonSettings.getSetting("include_media") == "true" + include_people = addonSettings.getSetting("include_people") == "true" + include_overview = addonSettings.getSetting("include_overview") == "true" + + detailsString = "EpisodeCount,SeasonCount,Path,Genres,Studios,CumulativeRunTimeTicks,Etag" + + if include_media: + detailsString += ",MediaStreams" + + if include_people: + detailsString += ",People" + + if include_overview: + detailsString += ",Overview" + + return detailsString + + +def getChecksum(item): + userdata = item['UserData'] + checksum = "%s_%s_%s_%s_%s_%s_%s" % ( + item['Etag'], + userdata['Played'], + userdata['IsFavorite'], + userdata.get('Likes', "-"), + userdata['PlaybackPositionTicks'], + userdata.get('UnplayedItemCount', "-"), + userdata.get("PlayedPercentage", "-") + ) + + return checksum + + +def getArt(item, server, widget=False): + art = { + 'thumb': '', + 'fanart': '', + 'poster': '', + 'banner': '', + 'clearlogo': '', + 'clearart': '', + 'discart': '', + 'landscape': '', + 'tvshow.poster': '' + } + item_id = item.get("Id") + + image_id = item_id + imageTags = item.get("ImageTags") + if (imageTags is not None) and (imageTags.get("Primary") is not None): + image_tag = imageTags.get("Primary") + if widget: + art['thumb'] = downloadUtils.imageUrl(image_id, "Primary", 0, 400, 400, image_tag, server=server) + else: + art['thumb'] = downloadUtils.getArtwork(item, "Primary", server=server) + + if item.get("Type") == "Episode": + art['thumb'] = art['thumb'] if art['thumb'] else downloadUtils.getArtwork(item, "Thumb", server=server) + art['landscape'] = art['thumb'] if art['thumb'] else downloadUtils.getArtwork(item, "Thumb", parent=True, server=server) + art['tvshow.poster'] = downloadUtils.getArtwork(item, "Primary", parent=True, server=server) + else: + art['poster'] = art['thumb'] + + art['fanart'] = downloadUtils.getArtwork(item, "Backdrop", server=server) + if not art['fanart']: + art['fanart'] = downloadUtils.getArtwork(item, "Backdrop", parent=True, server=server) + + if not art['landscape']: + art['landscape'] = downloadUtils.getArtwork(item, "Thumb", server=server) + if not art['landscape']: + art['landscape'] = art['fanart'] + + if not art['thumb']: + art['thumb'] = art['landscape'] + + art['banner'] = downloadUtils.getArtwork(item, "Banner", server=server) + art['clearlogo'] = downloadUtils.getArtwork(item, "Logo", server=server) + art['clearart'] = downloadUtils.getArtwork(item, "Art", server=server) + art['discart'] = downloadUtils.getArtwork(item, "Disc", server=server) + + return art diff --git a/plugin.video.embycon/resources/settings.xml b/plugin.video.embycon/resources/settings.xml new file mode 100644 index 0000000..4bb7a5a --- /dev/null +++ b/plugin.video.embycon/resources/settings.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<settings> + <category label="30014"> + <setting id="ipaddress" type="text" label="30000" default="<none>" visible="true" enable="true" /> + <setting id="port" type="text" label="30001" default="8096" visible="true" enable="true" /> + <setting id="use_https" type="bool" label="30002" default="false" visible="true" enable="true" /> + <setting id="verify_cert" type="bool" label="30003" default="false" visible="true" enable="true" /> + <setting label="30011" type="action" action="RunScript(plugin.video.embycon,0,?mode=DETECT_SERVER_USER)" option="close"/> + <setting type="sep" /> + <setting id="username" type="text" label="30024" /> + <setting id="password" type="text" option="hidden" label="30025" /> + <setting label="30012" type="action" action="RunScript(plugin.video.embycon,0,?mode=CHANGE_USER)" option="close"/> + <setting id="deviceName" type="text" label="30016" default="EmbyCon" visible="true" enable="true" /> + </category> + <category label="30207"> + <setting id="playback_type" type="enum" label="30206" lvalues="30209|30210|30211" default="1" /> + <setting label="30209" type="lsep"/> + <setting type="sep" /> + <setting id="smbusername" type="text" label="30007" default="" enable="true" visible="true"/> + <setting id="smbpassword" type="text" label="30008" default="" option="hidden" enable="true" visible="true"/> + <setting label="30211" type="lsep"/> + <setting type="sep" /> + <setting id="playback_bitrate" type="slider" label="30208" default="6000" range="400,100,10000" option="int" visible="true"/> + <setting id="playback_max_width" type="enum" label="30212" lvalues="640x|720x|1024x|1280x|1440x|1600x|1920x|2600x|4096x" default="6" visible="true"/> + <setting id="playback_video_force_8" type="bool" label="30213" default="false" visible="true" enable="true"/> + </category> + <category label="30214"> + <setting label="30215" type="lsep"/> + <setting type="sep" /> + <setting id="promptPlayNextEpisodePercentage" type="slider" label="30218" default="100" range="5,1,100" option="int" visible="true"/> + <setting id="promptDeleteEpisodePercentage" type="slider" label="30217" default="100" range="5,1,100" option="int" visible="true"/> + <setting id="promptDeleteMoviePercentage" type="slider" label="30220" default="100" range="5,1,100" option="int" visible="true"/> + <setting label="30121" type="lsep"/> + <setting type="sep" /> + <setting id="jump_back_amount" type="slider" label="30114" default="15" range="0,1,60" option="int" visible="true"/> + </category> + <category label="30110"> + <setting id="showLoadProgress" type="bool" label="30120" default="false" visible="true" enable="true" /> + <setting id="addCounts" type="bool" label="30116" default="true" visible="true" enable="true" /> + <setting id="addSeasonNumber" type="bool" label="30162" default="false" visible="true" enable="true" /> + <setting id="addEpisodeNumber" type="bool" label="30119" default="true" visible="true" enable="true" /> + <setting id="addResumePercent" type="bool" label="30118" default="true" visible="true" enable="true" /> + <setting id="addSubtitleAvailable" type="bool" label="30163" default="false" visible="true" enable="true" /> + <setting id="include_overview" type="bool" label="30181" default="true" visible="true" enable="true" /> + <setting id="include_media" type="bool" label="30182" default="true" visible="true" enable="true" /> + <setting id="include_people" type="bool" label="30183" default="false" visible="true" enable="true" /> + <setting id="flatten_single_season" type="bool" label="30020" default="true" visible="true" enable="true" /> + <setting id="show_x_filtered_items" type="slider" label="30018" default="20" range="5,1,100" option="int" visible="true"/> + <setting id="episode_name_format" type="text" default="{SeriesName} - s{SeasonIndex}e{EpisodeIndex} - {ItemName}" label="30019" /> + </category> + <category label="30022"> <!-- Advanced --> + <setting id="profile" type="bool" label="30010" default="false" visible="true" enable="true" /> + <setting id="cacheEmbyData" type="bool" label="30138" default="false" visible="true" enable="true" /> + </category> +</settings>
\ No newline at end of file diff --git a/plugin.video.embycon/resources/skins/default/720p/ResumeDialog.xml b/plugin.video.embycon/resources/skins/default/720p/ResumeDialog.xml new file mode 100644 index 0000000..9ff6bad --- /dev/null +++ b/plugin.video.embycon/resources/skins/default/720p/ResumeDialog.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<window id="3301" type="dialog"> + <defaultcontrol always="true">3010</defaultcontrol> + <zorder>2</zorder> + <coordinates> + <system>1</system> + <left>450</left> + <top>280</top> + </coordinates> + <include>dialogeffect</include> + <controls> + + <control type="image"> + <left>0</left> + <top>0</top> + <width>380</width> + <height>160</height> + <texture border="40">bg.png</texture> + </control> + + <control type="button" id="3010"> + <texturenofocus border="1" colordiffuse="ff161616">white.png</texturenofocus> + <texturefocus border="1" colordiffuse="ff525252">white.png</texturefocus> + <left>20</left> + <top>30</top> + <width>340</width> + <height>40</height> + <label></label> + <onup></onup> + <ondown>3011</ondown> + <font>font14</font> + <align>center</align> + </control> + + <control type="button" id="3011"> + <texturenofocus border="1" colordiffuse="ff161616">white.png</texturenofocus> + <texturefocus border="1" colordiffuse="ff525252">white.png</texturefocus> + <left>20</left> + <top>90</top> + <width>340</width> + <height>40</height> + <label></label> + <onup>3010</onup> + <ondown>3012</ondown> + <font>font14</font> + <align>center</align> + </control> + + </controls> +</window>
\ No newline at end of file diff --git a/plugin.video.embycon/resources/skins/default/media/bg.png b/plugin.video.embycon/resources/skins/default/media/bg.png Binary files differnew file mode 100644 index 0000000..68d7e61 --- /dev/null +++ b/plugin.video.embycon/resources/skins/default/media/bg.png diff --git a/plugin.video.embycon/resources/skins/default/media/white.png b/plugin.video.embycon/resources/skins/default/media/white.png Binary files differnew file mode 100644 index 0000000..136602c --- /dev/null +++ b/plugin.video.embycon/resources/skins/default/media/white.png diff --git a/plugin.video.embycon/resources/skins/skin.estuary/copy_home.txt b/plugin.video.embycon/resources/skins/skin.estuary/copy_home.txt new file mode 100644 index 0000000..1bb7077 --- /dev/null +++ b/plugin.video.embycon/resources/skins/skin.estuary/copy_home.txt @@ -0,0 +1,17 @@ + +Libreelec +Log into Libreelec and run the following commands and then restart Libreelec. + +cp -r /usr/share/kodi/addons/skin.estuary /storage/.kodi/addons + +cp /storage/.kodi/addons/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml /storage/.kodi/addons/skin.estuary/xml/Home.xml +or +cp /storage/.kodi/addons/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml /storage/.kodi/addons/skin.estuary/xml/Home.xml + +Windows + +xcopy /S /Y "c:\Program Files (x86)\Kodi\addons\skin.estuary\*" "%APPDATA%\Kodi\addons\skin.estuary\" + +xcopy /Y "%APPDATA%\Kodi\addons\plugin.video.embycon\resources\skins\skin.estuary\xml\Home-17.1.xml" "%APPDATA%\Kodi\addons\skin.estuary\xml\Home.xml" +or +xcopy /Y "%APPDATA%\Kodi\addons\plugin.video.embycon\resources\skins\skin.estuary\xml\Home-17.3.xml" "%APPDATA%\Kodi\addons\skin.estuary\xml\Home.xml" diff --git a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml new file mode 100644 index 0000000..ef46639 --- /dev/null +++ b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml @@ -0,0 +1,1061 @@ +<?xml version="1.0" encoding="UTF-8"?> +<window> + <defaultcontrol>9000</defaultcontrol> + <backgroundcolor>background</backgroundcolor> + <controls> + <control type="button" id="20000"> + <include>HiddenObject</include> + <animation effect="fade" time="300" start="100" end="0">Focus</animation> + <onfocus>SetFocus(2000)</onfocus> + <onclick>noop</onclick> + <visible allowhiddenfocus="true">Control.HasFocus(20000)</visible> + </control> + <control type="button" id="20001"> + <include>HiddenObject</include> + <animation effect="fade" time="300" start="100" end="0">Focus</animation> + <onfocus>SetFocus(2000)</onfocus> + <onclick>noop</onclick> + <visible allowhiddenfocus="true">Control.HasFocus(20001)</visible> + </control> + <include>DefaultBackground</include> + <control type="multiimage"> + <depth>DepthBackground</depth> + <include>FullScreenDimensions</include> + <aspectratio>scale</aspectratio> + <fadetime>600</fadetime> + <animation effect="zoom" center="auto" end="102,102" time="0" condition="Integer.IsGreater(System.StereoscopicMode,0)">conditional</animation> + <animation effect="fade" start="0" end="100" time="400">WindowOpen</animation> + <animation effect="fade" start="100" end="0" time="300">WindowClose</animation> + <animation effect="fade" time="400">VisibleChange</animation> + <imagepath background="true" colordiffuse="bg_overlay">$VAR[HomeFanartVar]</imagepath> + <visible>!Player.HasMedia</visible> + </control> + <control type="group"> + <animation effect="fade" start="100" end="0" time="200" tween="sine" condition="$EXP[infodialog_active]">Conditional</animation> + <control type="group" id="2000"> + <left>462</left> + <animation type="Conditional" condition="Control.IsVisible(20000)" reversible="false"> + <effect type="slide" end="0,20" time="60" tween="sine" /> + <effect type="slide" end="0,-20" time="180" tween="sine" delay="80" /> + </animation> + <animation type="Conditional" condition="Control.IsVisible(20001)" reversible="false"> + <effect type="slide" end="0,-20" time="60" tween="sine" /> + <effect type="slide" end="0,20" time="180" tween="sine" delay="80" /> + </animation> + <include>OpenClose_Right</include> + <!-- Start of Emby Widget Items --> + <control type="group" id="3000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),emby_movies)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="emby_movies"/> + </include> + <control type="grouplist" id="3001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListPoster" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=recent_movies"/> + <param name="widget_header" value="Recently Added"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="3800"/> + </include> + <include content="WidgetListPoster" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=inprogress_movies"/> + <param name="widget_header" value="In Progress"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="3900"/> + </include> + <include content="WidgetListPoster" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=random_movies"/> + <param name="widget_header" value="Random"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="3100"/> + </include> + </control> + </control> + <control type="group" id="4000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),emby_tvshows)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="emby_tvshows"/> + </include> + <control type="grouplist" id="4001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListEpisodes" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=recent_episodes"/> + <param name="widget_header" value="Recently Added"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="4900"/> + </include> + <include content="WidgetListEpisodes" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=inprogress_episodes"/> + <param name="widget_header" value="In Progress"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="4100"/> + </include> + <include content="WidgetListEpisodes" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=nextup_episodes"/> + <param name="widget_header" value="Next Up"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="4200"/> + </include> + </control> + </control> + <!-- End of Emby Widget Items --> + <control type="group" id="5000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),movies)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="movies"/> + </include> + <control type="grouplist" id="5001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="Library.HasContent(movies)"> + <param name="content_path" value="library://video/movies/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5900"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="special://skin/playlists/inprogress_movies.xsp"/> + <param name="widget_header" value="$LOCALIZE[31010]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5100"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="special://skin/playlists/recent_unwatched_movies.xsp"/> + <param name="widget_header" value="$LOCALIZE[20386]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5200"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="special://skin/playlists/unwatched_movies.xsp"/> + <param name="widget_header" value="$LOCALIZE[31007]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5300"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="special://skin/playlists/random_movies.xsp"/> + <param name="widget_header" value="$LOCALIZE[31006]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5400"/> + </include> + <include content="WidgetListCategories" condition="Library.HasContent(movies)"> + <param name="content_path" value="videodb://movies/genres/"/> + <param name="widget_header" value="$LOCALIZE[135]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5500"/> + <param name="icon" value="$VAR[WidgetGenreIconVar]"/> + <param name="icon_height" value="70"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="videodb://movies/sets/"/> + <param name="widget_header" value="$LOCALIZE[31075]"/> + <param name="widget_target" value="videos"/> + <param name="sortby" value="random"/> + <param name="list_id" value="5600"/> + </include> + </control> + <include content="ImageWidget" condition="!Library.HasContent(movies)"> + <param name="text_label" value="$LOCALIZE[31104]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(videos,files,return)"/> + <param name="button_id" value="5500"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMovieButton)"/> + </include> + </control> + <control type="group" id="6000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),tvshows)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="tvshows"/> + </include> + <control type="grouplist" id="6001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="library://video/tvshows/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6900"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="videodb://inprogresstvshows"/> + <param name="sortby" value="lastplayed"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[626]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6100"/> + </include> + <include content="WidgetListEpisodes" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="special://skin/playlists/recent_unwatched_episodes.xsp"/> + <param name="widget_header" value="$LOCALIZE[20387]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6200"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="special://skin/playlists/unwatched_tvshows.xsp"/> + <param name="widget_header" value="$LOCALIZE[31122]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6300"/> + </include> + <include content="WidgetListCategories" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="videodb://tvshows/genres/"/> + <param name="widget_header" value="$LOCALIZE[135]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6400"/> + <param name="icon" value="$VAR[WidgetGenreIconVar]"/> + <param name="icon_height" value="70"/> + </include> + <include content="WidgetListCategories" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="videodb://tvshows/studios/"/> + <param name="widget_header" value="$LOCALIZE[20388]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6500"/> + <param name="icon" value="$VAR[WidgetStudioIconVar]"/> + <param name="icon_height" value="70"/> + </include> + </control> + <include content="ImageWidget" condition="!Library.HasContent(tvshows)"> + <param name="text_label" value="$LOCALIZE[31104]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(videos,files,return)"/> + <param name="button_id" value="6400"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoTVShowButton)"/> + </include> + </control> + <control type="group" id="7000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),music)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="music"/> + </include> + <control type="grouplist" id="7001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="Library.HasContent(music)"> + <param name="content_path" value="library://music/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7900"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="musicdb://recentlyplayedalbums"/> + <param name="widget_header" value="$LOCALIZE[517]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7100"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="musicdb://recentlyaddedalbums/"/> + <param name="widget_header" value="$LOCALIZE[359]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7200"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="special://skin/playlists/random_albums.xsp"/> + <param name="widget_header" value="$LOCALIZE[31012]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7300"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="special://skin/playlists/random_artists.xsp"/> + <param name="widget_header" value="$LOCALIZE[31013]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7400"/> + <param name="fallback_icon" value="DefaultMusicArtists.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="special://skin/playlists/unplayed_albums.xsp"/> + <param name="widget_header" value="$LOCALIZE[31014]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7500"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="special://skin/playlists/mostplayed_albums.xsp"/> + <param name="widget_header" value="$LOCALIZE[31011]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7600"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + <param name="sortby" value="playcount"/> + <param name="sortorder" value="descending"/> + </include> + </control> + <include content="ImageWidget" condition="!Library.HasContent(music)"> + <param name="text_label" value="$LOCALIZE[31104]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(music,files)"/> + <param name="button_id" value="7600"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMusicButton)"/> + </include> + </control> + <control type="group" id="8000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),addons)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="addons"/> + </include> + <control type="grouplist" id="8001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories"> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="list_id" value="8900"/> + <param name="visible" value="Integer.IsGreater(Container(8100).NumItems,0) | Integer.IsGreater(Container(8200).NumItems,0) | Integer.IsGreater(Container(8300).NumItems,0) | Integer.IsGreater(Container(8400).NumItems,0) | Integer.IsGreater(Container(8500).NumItems,0)"/> + <param name="addon_submenu" value="true"/> + </include> + <include content="WidgetListSquare"> + <param name="content_path" value="addons://sources/video/"/> + <param name="widget_header" value="$LOCALIZE[1037]"/> + <param name="widget_target" value="videos"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8100"/> + <param name="fallback_icon" value="DefaultAddon.png"/> + </include> + <include content="WidgetListSquare"> + <param name="content_path" value="addons://sources/audio/"/> + <param name="widget_header" value="$LOCALIZE[1038]"/> + <param name="widget_target" value="music"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8200"/> + <param name="fallback_icon" value="DefaultAddon.png"/> + </include> + <include content="WidgetListSquare"> + <param name="content_path" value="addons://sources/executable/"/> + <param name="widget_header" value="$LOCALIZE[1043]"/> + <param name="widget_target" value="programs"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8300"/> + <param name="fallback_icon" value="DefaultAddon.png"/> + </include> + <include content="WidgetListSquare" condition="System.Platform.Android"> + <param name="content_path" value="androidapp://sources/apps/"/> + <param name="widget_header" value="$LOCALIZE[20244]"/> + <param name="widget_target" value="programs"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8400"/> + <param name="fallback_icon" value="DefaultAddon.png"/> + </include> + <include content="WidgetListSquare"> + <param name="content_path" value="addons://sources/image/"/> + <param name="widget_header" value="$LOCALIZE[1039]"/> + <param name="widget_target" value="pictures"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8500"/> + </include> + </control> + <include content="ImageWidget"> + <param name="text_label" value="$LOCALIZE[31119]" /> + <param name="button_label" value="$LOCALIZE[31118]" /> + <param name="button_onclick" value="ActivateWindow(addonbrowser)"/> + <param name="button_id" value="8600"/> + <param name="visible" value="!Integer.IsGreater(Container(8001).NumItems,0)"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoProgramsButton)"/> + </include> + </control> + <control type="group" id="11000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),video)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="video"/> + </include> + <control type="grouplist" id="11001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories"> + <param name="content_path" value="library://video/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="11900"/> + </include> + <include content="WidgetListCategories"> + <param name="content_path" value="sources://video/"/> + <param name="widget_header" value="$LOCALIZE[20094]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="11100"/> + </include> + <include content="WidgetListCategories"> + <param name="content_path" value="special://videoplaylists/"/> + <param name="widget_header" value="$LOCALIZE[136]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="11200"/> + <param name="icon" value="DefaultPlaylist.png"/> + </include> + </control> + <include content="ImageWidget"> + <param name="text_label" value="$LOCALIZE[31105]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(videos,root)"/> + <param name="button_id" value="11300"/> + <param name="visible" value="!Integer.IsGreater(Container(11001).NumItems,0)"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoVideosButton)"/> + </include> + </control> + <control type="group" id="12000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),livetv)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="livetv"/> + </include> + <control type="grouplist" id="12001"> + <include>WidgetGroupListCommon</include> + <control type="grouplist" id="12855"> + <height>390</height> + <left>0</left> + <right>0</right> + <top>36</top> + <orientation>horizontal</orientation> + <visible>PVR.IsRecordingTV | PVR.HasNonRecordingTVTimer</visible> + <align>center</align> + <control type="group"> + <width>674</width> + <visible>PVR.IsRecordingTV</visible> + <include content="PVRWidget"> + <param name="icon" value="$INFO[PVR.TVNowRecordingChannelIcon]" /> + <param name="header" value="$LOCALIZE[19158]" /> + <param name="label1" value="$INFO[PVR.TVNowRecordingDateTime]" /> + <param name="label2" value="$INFO[PVR.TVNowRecordingTitle][CR][COLOR=grey]$INFO[PVR.TVNowRecordingChannel][/COLOR]" /> + </include> + </control> + <control type="group"> + <width>674</width> + <visible>PVR.HasNonRecordingTVTimer</visible> + <include content="PVRWidget"> + <param name="icon" value="$INFO[PVR.TVNextRecordingChannelIcon]" /> + <param name="header" value="$LOCALIZE[19157]" /> + <param name="label1" value="$INFO[PVR.TVNextRecordingDateTime]" /> + <param name="label2" value="$INFO[PVR.TVNextRecordingTitle][CR][COLOR=grey]$INFO[PVR.TVNextRecordingChannel][/COLOR]" /> + </include> + </control> + </control> + <include content="WidgetListCategories" condition="System.HasPVRAddon"> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="list_id" value="12900"/> + <param name="pvr_submenu" value="true"/> + <param name="pvr_type" value="TV"/> + </include> + <include content="WidgetListChannels" condition="System.HasPVRAddon"> + <param name="content_path" value="pvr://channels/tv/*?view=lastplayed"/> + <param name="sortby" value="lastplayed"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[31016]"/> + <param name="widget_target" value="pvr"/> + <param name="list_id" value="12200"/> + </include> + <include content="WidgetListChannels" condition="System.HasPVRAddon"> + <param name="content_path" value="pvr://recordings/tv/active?view=flat"/> + <param name="sortby" value="date"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[31015]"/> + <param name="widget_target" value="pvr"/> + <param name="list_id" value="12300"/> + <param name="label" value="$INFO[ListItem.ChannelName]"/> + <param name="label2" value="$INFO[ListItem.Title]$INFO[ListItem.EpisodeName, (,)]"/> + </include> + </control> + <include content="ImageWidget" condition="!System.HasPVRAddon"> + <param name="text_label" value="$LOCALIZE[31143]" /> + <param name="button_label" value="$LOCALIZE[31144]" /> + <param name="button_onclick" value="ActivateWindow(addonbrowser,addons://user/xbmc.pvrclient,return)"/> + <param name="button_id" value="12400"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoTVButton)"/> + </include> + </control> + <control type="group" id="13000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),radio)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="radio"/> + </include> + <control type="grouplist" id="13001"> + <include>WidgetGroupListCommon</include> + <control type="grouplist" id="13855"> + <height>390</height> + <left>25</left> + <top>36</top> + <orientation>horizontal</orientation> + <align>right</align> + <width>1360</width> + <visible>PVR.IsRecordingRadio | PVR.HasNonRecordingRadioTimer</visible> + <control type="group"> + <width>680</width> + <visible>PVR.IsRecordingRadio</visible> + <include content="PVRWidget"> + <param name="icon" value="$INFO[PVR.RadioNowRecordingChannelIcon]" /> + <param name="header" value="$LOCALIZE[19158]" /> + <param name="label1" value="$INFO[PVR.RadioNowRecordingDateTime]" /> + <param name="label2" value="$INFO[PVR.RadioNowRecordingTitle][CR][COLOR=grey]$INFO[PVR.RadioNowRecordingChannel][/COLOR]" /> + </include> + </control> + <control type="group"> + <visible>PVR.HasNonRecordingRadioTimer</visible> + <width>680</width> + <include content="PVRWidget"> + <param name="icon" value="$INFO[PVR.RadioNextRecordingChannelIcon]" /> + <param name="header" value="$LOCALIZE[19157]" /> + <param name="label1" value="$INFO[PVR.RadioNextRecordingDateTime]" /> + <param name="label2" value="$INFO[PVR.RadioNextRecordingTitle][CR][COLOR=grey]$INFO[PVR.RadioNextRecordingChannel][/COLOR]" /> + </include> + </control> + </control> + <include content="WidgetListCategories" condition="System.HasPVRAddon"> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="list_id" value="13900"/> + <param name="pvr_submenu" value="true"/> + <param name="pvr_type" value="Radio"/> + </include> + <include content="WidgetListChannels" condition="System.HasPVRAddon"> + <param name="content_path" value="pvr://channels/radio/*?view=lastplayed"/> + <param name="sortby" value="lastplayed"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[31018]"/> + <param name="widget_target" value="files"/> + <param name="list_id" value="13200"/> + </include> + <include content="WidgetListChannels" condition="System.HasPVRAddon"> + <param name="content_path" value="pvr://recordings/radio/active?view=flat"/> + <param name="sortby" value="date"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[31015]"/> + <param name="widget_target" value="pvr"/> + <param name="list_id" value="13300"/> + <param name="label" value="$INFO[ListItem.ChannelName]"/> + <param name="label2" value="$INFO[ListItem.Title]$INFO[ListItem.EpisodeName, (,)]"/> + </include> + </control> + <include content="ImageWidget" condition="!System.HasPVRAddon"> + <param name="text_label" value="$LOCALIZE[31143]" /> + <param name="button_label" value="$LOCALIZE[31144]" /> + <param name="button_onclick" value="ActivateWindow(addonbrowser,addons://user/xbmc.pvrclient,return)"/> + <param name="button_id" value="13400"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoRadioButton)"/> + </include> + </control> + <control type="group" id="14000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),favorites)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="favorites"/> + </include> + <control type="panel" id="14100"> + <left>65</left> + <top>0</top> + <right>0</right> + <bottom>0</bottom> + <onleft>9000</onleft> + <onright>9000</onright> + <onup>14100</onup> + <ondown>14100</ondown> + <onclick>$INFO[ListItem.FileNameAndPath]</onclick> + <preloaditems>2</preloaditems> + <scrolltime tween="cubic" easing="out">500</scrolltime> + <orientation>vertical</orientation> + <visible>Integer.IsGreater(Container(14100).NumItems,0) | Container(14100).IsUpdating</visible> + <itemlayout width="330" height="396"> + <control type="group"> + <top>130</top> + <include content="InfoWallMusicLayout"> + <param name="fallback_image" value="DefaultFavourites.png" /> + <param name="focused" value="false" /> + </include> + </control> + </itemlayout> + <focusedlayout width="330" height="396"> + <control type="group"> + <depth>DepthContentPopout</depth> + <top>130</top> + <animation effect="zoom" start="100" end="110" time="200" tween="sine" easing="inout" center="170,320">Focus</animation> + <animation effect="zoom" start="110" end="100" time="200" tween="sine" easing="inout" center="170,320">UnFocus</animation> + <include content="InfoWallMusicLayout"> + <param name="fallback_image" value="DefaultFavourites.png" /> + <param name="focused" value="true" /> + </include> + </control> + </focusedlayout> + <content>favourites://</content> + </control> + <include content="ImageWidget"> + <param name="text_label" value="$LOCALIZE[31025]" /> + <param name="button_label" value="$LOCALIZE[31116]" /> + <param name="button_onclick" value=""/> + <param name="button_id" value="5500"/> + <param name="visible" value="!Integer.IsGreater(Container(14100).NumItems,0) + !Container(14100).IsUpdating"/> + <param name="visible_1" value="false"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoFavButton)"/> + </include> + </control> + <control type="group" id="15000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),weather)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="weather"/> + </include> + <control type="grouplist" id="15001"> + <include>WidgetGroupListCommon</include> + <control type="group" id="16678"> + <description>Weather info</description> + <left>68</left> + <right>70</right> + <top>102</top> + <height>300</height> + <visible>!String.IsEmpty(Weather.plugin)</visible> + <control type="image"> + <bottom>90</bottom> + <width>100%</width> + <texture border="21">dialogs/dialog-bg.png</texture> + </control> + <control type="label"> + <left>840</left> + <top>60</top> + <aligny>center</aligny> + <height>24</height> + <right>60</right> + <align>right</align> + <font>font30_title</font> + <label>$INFO[Weather.Location]</label> + </control> + <control type="label"> + <left>840</left> + <top>120</top> + <aligny>center</aligny> + <height>24</height> + <right>60</right> + <align>right</align> + <font>font14</font> + <label>$INFO[Weather.Conditions,, ∙ ]$INFO[Weather.Temperature]</label> + </control> + <control type="grouplist"> + <top>50</top> + <left>50</left> + <right>20</right> + <orientation>horizontal</orientation> + <align>left</align> + <itemgap>-110</itemgap> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Current.Wind)" /> + <param name="texture" value="icons/weather/wind.png" /> + <param name="header" value="404" /> + </include> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Current.Humidity)" /> + <param name="texture" value="icons/weather/humidity.png" /> + <param name="header" value="406" /> + </include> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Current.Precipitation)" /> + <param name="texture" value="icons/weather/rain.png" /> + <param name="header" value="33021" /> + </include> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Today.Sunrise)" /> + <param name="texture" value="icons/weather/sunrise.png" /> + <param name="header" value="405" /> + </include> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Today.Sunset)" /> + <param name="texture" value="icons/weather/sunset.png" /> + <param name="header" value="403" /> + </include> + </control> + </control> + <include content="WeatherWidget" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="content_include" value="DailyItems" /> + <param name="list_id" value="15200" /> + <param name="widget_header" value="$LOCALIZE[31019]"/> + <param name="visible" value="!String.IsEmpty(Window(weather).Property(Daily.IsFetched))" /> + </include> + <include content="WeatherWidget" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="content_include" value="HourlyItems" /> + <param name="list_id" value="15100" /> + <param name="widget_header" value="$LOCALIZE[33036]"/> + <param name="visible" value="!String.IsEmpty(Window(weather).Property(Hourly.IsFetched))" /> + </include> + </control> + <include content="ImageWidget" condition="String.IsEmpty(Weather.plugin)"> + <param name="text_label" value="$LOCALIZE[31120]" /> + <param name="button_label" value="$LOCALIZE[31121]" /> + <param name="button_onclick" value="ActivateWindow(servicesettings,weather)"/> + <param name="button_id" value="15300"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoWeatherButton)"/> + </include> + </control> + <control type="group" id="16000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),musicvideos)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="musicvideos"/> + </include> + <control type="grouplist" id="16001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="library://music/musicvideos/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="16900"/> + </include> + <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="videodb://recentlyaddedmusicvideos/"/> + <param name="widget_header" value="$LOCALIZE[20390]"/> + <param name="widget_target" value="videos"/> + <param name="main_label" value="$INFO[ListItem.Label]" /> + <param name="sub_label" value="$INFO[ListItem.Artist]" /> + <param name="thumb_label" value="$INFO[ListItem.Year]" /> + <param name="fallback_image" value="DefaultMusicSongs.png" /> + <param name="list_id" value="16300"/> + </include> + <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="special://skin/playlists/unwatched_musicvideos.xsp"/> + <param name="widget_header" value="$LOCALIZE[31151]"/> + <param name="widget_target" value="videos"/> + <param name="main_label" value="$INFO[ListItem.Label]" /> + <param name="sub_label" value="$INFO[ListItem.Artist]" /> + <param name="thumb_label" value="$INFO[ListItem.Year]" /> + <param name="fallback_image" value="DefaultMusicSongs.png" /> + <param name="list_id" value="16400"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="special://skin/playlists/random_musicvideo_artists.xsp"/> + <param name="widget_header" value="$LOCALIZE[31013]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="16200"/> + <param name="widget_limit" value="10"/> + </include> + <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="special://skin/playlists/random_musicvideos.xsp"/> + <param name="widget_header" value="$LOCALIZE[31152]"/> + <param name="widget_target" value="videos"/> + <param name="main_label" value="$INFO[ListItem.Label]" /> + <param name="sub_label" value="$INFO[ListItem.Artist]" /> + <param name="thumb_label" value="$INFO[ListItem.Year]" /> + <param name="fallback_image" value="DefaultMusicSongs.png" /> + <param name="list_id" value="16500"/> + </include> + <include content="WidgetListCategories" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="videodb://musicvideos/studios/"/> + <param name="widget_header" value="$LOCALIZE[20388]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="16600"/> + <param name="icon" value="$VAR[WidgetStudioIconVar]"/> + <param name="icon_height" value="70"/> + </include> + </control> + <include content="ImageWidget" condition="!Library.HasContent(musicvideos)"> + <param name="text_label" value="$LOCALIZE[31104]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(videos,files,return)"/> + <param name="button_id" value="16800"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMusicVideoButton)"/> + </include> + </control> + <control type="group" id="4000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),pictures)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="pictures"/> + </include> + <control type="grouplist" id="4001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="!Skin.HasSetting(HomeMenuNoPicturesButton)"> + <param name="content_path" value="sources://pictures/"/> + <param name="widget_header" value="$LOCALIZE[20094]"/> + <param name="widget_target" value="pictures"/> + <param name="list_id" value="4100"/> + </include> + </control> + </control> + <control type="group" id="21000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),disc)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="disc"/> + </include> + <include content="ImageWidget"> + <param name="text_label" value="$INFO[System.DVDLabel]" /> + <param name="button_label" value="$LOCALIZE[341]" /> + <param name="button_onclick" value="PlayDisc"/> + <param name="button_id" value="21100"/> + <param name="visible" value="true"/> + <param name="button2_label" value="$LOCALIZE[13391]"/> + <param name="button2_onclick" value="EjectTray()"/> + </include> + </control> + </control> + <control type="group"> + <depth>DepthContentPanel</depth> + <include>OpenClose_Left</include> + <include content="ContentPanel"> + <param name="width" value="522" /> + </include> + <control type="fixedlist" id="9000"> + <left>0</left> + <top>240</top> + <width>462</width> + <bottom>-10</bottom> + <movement>6</movement> + <focusposition>1</focusposition> + <onfocus>ClearProperty(listposition,home)</onfocus> + <onright>SetFocus($INFO[Container(9000).ListItem.Property(menu_id)])</onright> + <onup>700</onup> + <ondown>700</ondown> + <scrolltime tween="cubic" easing="out">500</scrolltime> + <focusedlayout height="95"> + <control type="group"> + <animation effect="fade" start="100" end="0" time="0">UnFocus</animation> + <control type="image"> + <left>0</left> + <top>0</top> + <width>462</width> + <height>95</height> + <texture colordiffuse="button_focus">lists/focus.png</texture> + <animation effect="fade" start="100" end="0" time="0" condition="[!Control.HasFocus(9000) + !ControlGroup(700).HasFocus] | System.HasModalDialog">Conditional</animation> + </control> + <control type="image"> + <left>-3</left> + <top>1</top> + <width>95</width> + <height>95</height> + <texture colordiffuse="button_focus">$INFO[ListItem.Art(thumb)]</texture> + <animation effect="fade" start="0" end="100" time="300" reversible="false">Focus</animation> + </control> + <control type="image"> + <left>0</left> + <top>0</top> + <width>95</width> + <height>95</height> + <texture colordiffuse="51FFFFFF">colors/black.png</texture> + <animation effect="fade" start="100" end="0" time="0" condition="[!Control.HasFocus(9000) + !ControlGroup(700).HasFocus] | System.HasModalDialog">Conditional</animation> + </control> + </control> + <control type="image"> + <left>-3</left> + <top>1</top> + <width>95</width> + <height>95</height> + <texture>$INFO[ListItem.Art(thumb)]</texture> + </control> + <control type="label"> + <left>104</left> + <top>0</top> + <height>95</height> + <width>560</width> + <aligny>center</aligny> + <font>font37</font> + <label>$INFO[ListItem.Label]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + </focusedlayout> + <itemlayout height="95"> + <control type="image"> + <left>-3</left> + <top>1</top> + <width>95</width> + <height>95</height> + <texture colordiffuse="44FFFFFF">$INFO[ListItem.Art(thumb)]</texture> + </control> + <control type="label"> + <left>104</left> + <top>0</top> + <height>95</height> + <width>560</width> + <aligny>center</aligny> + <font>font37</font> + <label>$INFO[ListItem.Label]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + </itemlayout> + <content> + <!-- Start Emby Menu Items --> + <item> + <label>Emby Movies</label> + <onclick>SetProperty(plugin.video.embycon-Params, mode=SHOW_CONTENT&item_type=Movie&media_type=movies, 10000)</onclick> + <onclick>RunAddon(plugin.video.embycon)</onclick> + <property name="menu_id">$NUMBER[3000]</property> + <thumb>icons/sidemenu/movies.png</thumb> + <property name="id">emby_movies</property> + </item> + <item> + <label>Emby TV Shows</label> + <onclick>SetProperty(plugin.video.embycon-Params, mode=SHOW_CONTENT&item_type=Series&media_type=tvshows, 10000)</onclick> + <onclick>RunAddon(plugin.video.embycon)</onclick> + <property name="menu_id">$NUMBER[4000]</property> + <thumb>icons/sidemenu/tv.png</thumb> + <property name="id">emby_tvshows</property> + </item> + <!-- End Emby Menu Items --> + <item> + <label>$LOCALIZE[342]</label> + <onclick condition="Library.HasContent(movies)">ActivateWindow(Videos,videodb://movies/titles/,return)</onclick> + <onclick condition="!Library.HasContent(movies)">ActivateWindow(Videos,sources://video/,return)</onclick> + <property name="menu_id">$NUMBER[5000]</property> + <thumb>icons/sidemenu/movies.png</thumb> + <property name="id">movies</property> + <visible>!Skin.HasSetting(HomeMenuNoMovieButton)</visible> + </item> + <item> + <label>$LOCALIZE[20343]</label> + <onclick condition="Library.HasContent(tvshows)">ActivateWindow(Videos,videodb://tvshows/titles/,return)</onclick> + <onclick condition="!Library.HasContent(tvshows)">ActivateWindow(Videos,sources://video/,return)</onclick> + <property name="menu_id">$NUMBER[6000]</property> + <thumb>icons/sidemenu/tv.png</thumb> + <property name="id">tvshows</property> + <visible>!Skin.HasSetting(HomeMenuNoTVShowButton)</visible> + </item> + <item> + <label>$LOCALIZE[2]</label> + <onclick>ActivateWindow(Music,root,return)</onclick> + <property name="menu_id">$NUMBER[7000]</property> + <thumb>icons/sidemenu/music.png</thumb> + <property name="id">music</property> + <visible>!Skin.HasSetting(HomeMenuNoMusicButton)</visible> + </item> + <item> + <label>$LOCALIZE[427]</label> + <onclick>PlayDisc</onclick> + <property name="menu_id">$NUMBER[21000]</property> + <thumb>icons/sidemenu/disc.png</thumb> + <property name="id">disc</property> + <visible>System.HasMediaDVD</visible> + </item> + <item> + <label>$LOCALIZE[20389]</label> + <property name="menu_id">$NUMBER[16000]</property> + <onclick>ActivateWindow(Videos,musicvideos,return)</onclick> + <thumb>icons/sidemenu/musicvideos.png</thumb> + <property name="id">musicvideos</property> + <visible>!Skin.HasSetting(HomeMenuNoMusicVideoButton)</visible> + </item> + <item> + <label>$LOCALIZE[19020]</label> + <property name="menu_id">$NUMBER[12000]</property> + <onclick>ActivateWindow(TVChannels)</onclick> + <thumb>icons/sidemenu/livetv.png</thumb> + <property name="id">livetv</property> + <visible>!Skin.HasSetting(HomeMenuNoTVButton)</visible> + </item> + <item> + <label>$LOCALIZE[19021]</label> + <property name="menu_id">$NUMBER[13000]</property> + <onclick>ActivateWindow(RadioChannels)</onclick> + <thumb>icons/sidemenu/radio.png</thumb> + <property name="id">radio</property> + <visible>!Skin.HasSetting(HomeMenuNoRadioButton)</visible> + </item> + <item> + <label>$LOCALIZE[24001]</label> + <property name="menu_id">$NUMBER[8000]</property> + <onclick>ActivateWindow(1100)</onclick> + <thumb>icons/sidemenu/addons.png</thumb> + <property name="id">addons</property> + <visible>!Skin.HasSetting(HomeMenuNoProgramsButton)</visible> + </item> + <item> + <label>$LOCALIZE[1]</label> + <onclick>ActivateWindow(Pictures)</onclick> + <property name="menu_id">$NUMBER[4000]</property> + <thumb>icons/sidemenu/pictures.png</thumb> + <property name="id">pictures</property> + <visible>!Skin.HasSetting(HomeMenuNoPicturesButton)</visible> + </item> + <item> + <label>$LOCALIZE[3]</label> + <onclick>ActivateWindow(Videos,root)</onclick> + <property name="menu_id">$NUMBER[11000]</property> + <thumb>icons/sidemenu/videos.png</thumb> + <property name="id">video</property> + <visible>!Skin.HasSetting(HomeMenuNoVideosButton)</visible> + </item> + <item> + <label>$LOCALIZE[10134]</label> + <onclick>ActivateWindow(favourites)</onclick> + <property name="menu_id">$NUMBER[14000]</property> + <thumb>icons/sidemenu/favourites.png</thumb> + <property name="id">favorites</property> + <visible>!Skin.HasSetting(HomeMenuNoFavButton)</visible> + </item> + <item> + <label>$LOCALIZE[8]</label> + <onclick condition="!String.IsEmpty(Weather.Plugin)">ActivateWindow(Weather)</onclick> + <onclick condition="String.IsEmpty(Weather.Plugin)">ReplaceWindow(servicesettings,weather)</onclick> + <property name="menu_id">$NUMBER[15000]</property> + <thumb>icons/sidemenu/weather.png</thumb> + <property name="id">weather</property> + <visible>!Skin.HasSetting(HomeMenuNoWeatherButton)</visible> + </item> + </content> + </control> + <control type="grouplist" id="700"> + <orientation>horizontal</orientation> + <itemgap>0</itemgap> + <left>-8</left> + <width>480</width> + <height>110</height> + <top>100</top> + <onup>SetFocus(9000)</onup> + <onup>PageDown</onup> + <onup>PageDown</onup> + <ondown>SetFocus(9000)</ondown> + <ondown>PageUp</ondown> + <ondown>PageUp</ondown> + <onright>2000</onright> + <align>justify</align> + <include content="IconButton"> + <param name="control_id" value="804" /> + <param name="onclick" value="ActivateWindow(shutdownmenu)" /> + <param name="icon" value="icons/power.png" /> + <param name="label" value="$LOCALIZE[33060]" /> + </include> + <include content="IconButton"> + <param name="control_id" value="802" /> + <param name="onclick" value="ActivateWindow(settings)" /> + <param name="icon" value="icons/settings.png" /> + <param name="label" value="$LOCALIZE[21417]" /> + </include> + <include content="IconButton"> + <param name="control_id" value="801" /> + <param name="onclick" value="ActivateWindow(1107)" /> + <param name="icon" value="icons/search.png" /> + <param name="label" value="$LOCALIZE[137]" /> + </include> + <include content="IconButton"> + <param name="control_id" value="803" /> + <param name="onclick" value="Fullscreen" /> + <param name="icon" value="icons/now-playing/fullscreen.png" /> + <param name="label" value="$LOCALIZE[31000]" /> + <param name="visible" value="Player.HasMedia" /> + </include> + </control> + </control> + <include>BottomBar</include> + <include content="TopBar"> + <param name="breadcrumbs_label" value="" /> + </include> + <control type="group"> + <depth>DepthBars</depth> + <animation effect="slide" end="0,-90" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation> + <animation effect="fade" start="0" end="100" time="300">WindowOpen</animation> + <animation effect="fade" start="100" end="0" time="200">WindowClose</animation> + <top>30</top> + <left>90</left> + <control type="image"> + <aspectratio>keep</aspectratio> + <width>56</width> + <height>56</height> + <texture colordiffuse="button_focus">icons/logo.png</texture> + </control> + <control type="image"> + <left>40</left> + <top>10</top> + <aspectratio>keep</aspectratio> + <width>192</width> + <height>36</height> + <texture>icons/logo-text.png</texture> + </control> + </control> + <control type="rss"> + <animation effect="slide" end="0,90" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation> + <animation effect="fade" start="0" end="100" time="400">WindowOpen</animation> + <animation effect="fade" start="100" end="0" time="300">WindowClose</animation> + <left>0</left> + <bottom>0</bottom> + <height>39</height> + <width>100%</width> + <font>font12</font> + <urlset>1</urlset> + <hitrect x="-100" y="0" w="1" h="1" /> + <titlecolor>button_focus</titlecolor> + <textcolor>button_focus</textcolor> + <shadowcolor>text_shadow</shadowcolor> + <headlinecolor>FFC0C0C0</headlinecolor> + </control> + </control> + </controls> +</window> diff --git a/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml new file mode 100644 index 0000000..f12f5c5 --- /dev/null +++ b/plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml @@ -0,0 +1,1059 @@ +<?xml version="1.0" encoding="UTF-8"?> +<window> + <defaultcontrol>9000</defaultcontrol> + <backgroundcolor>background</backgroundcolor> + <controls> + <control type="button" id="20000"> + <include>HiddenObject</include> + <animation effect="fade" time="300" start="100" end="0">Focus</animation> + <onfocus>SetFocus(2000)</onfocus> + <onclick>noop</onclick> + <visible allowhiddenfocus="true">Control.HasFocus(20000)</visible> + </control> + <control type="button" id="20001"> + <include>HiddenObject</include> + <animation effect="fade" time="300" start="100" end="0">Focus</animation> + <onfocus>SetFocus(2000)</onfocus> + <onclick>noop</onclick> + <visible allowhiddenfocus="true">Control.HasFocus(20001)</visible> + </control> + <include>DefaultBackground</include> + <control type="multiimage"> + <depth>DepthBackground</depth> + <include>FullScreenDimensions</include> + <aspectratio>scale</aspectratio> + <fadetime>600</fadetime> + <animation effect="zoom" center="auto" end="102,102" time="0" condition="Integer.IsGreater(System.StereoscopicMode,0)">conditional</animation> + <animation effect="fade" start="0" end="100" time="400">WindowOpen</animation> + <animation effect="fade" start="100" end="0" time="300">WindowClose</animation> + <animation effect="fade" time="400">VisibleChange</animation> + <imagepath background="true" colordiffuse="bg_overlay">$VAR[HomeFanartVar]</imagepath> + <visible>!Player.HasMedia</visible> + </control> + <control type="group"> + <animation effect="fade" start="100" end="0" time="200" tween="sine" condition="$EXP[infodialog_active]">Conditional</animation> + <control type="group" id="2000"> + <left>462</left> + <animation type="Conditional" condition="Control.IsVisible(20000)" reversible="false"> + <effect type="slide" end="0,20" time="60" tween="sine" /> + <effect type="slide" end="0,-20" time="180" tween="sine" delay="80" /> + </animation> + <animation type="Conditional" condition="Control.IsVisible(20001)" reversible="false"> + <effect type="slide" end="0,-20" time="60" tween="sine" /> + <effect type="slide" end="0,20" time="180" tween="sine" delay="80" /> + </animation> + <include>OpenClose_Right</include> + <!-- Start of Emby Widget Items --> + <control type="group" id="3000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),emby_movies)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="emby_movies"/> + </include> + <control type="grouplist" id="3001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListPoster" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=recent_movies"/> + <param name="widget_header" value="Recently Added"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="3800"/> + </include> + <include content="WidgetListPoster" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=inprogress_movies"/> + <param name="widget_header" value="In Progress"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="3900"/> + </include> + <include content="WidgetListPoster" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=random_movies"/> + <param name="widget_header" value="Random"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="3100"/> + </include> + </control> + </control> + <control type="group" id="4000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),emby_tvshows)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="emby_tvshows"/> + </include> + <control type="grouplist" id="4001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListEpisodes" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=recent_episodes"/> + <param name="widget_header" value="Recently Added"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="4900"/> + </include> + <include content="WidgetListEpisodes" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=inprogress_episodes"/> + <param name="widget_header" value="In Progress"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="4100"/> + </include> + <include content="WidgetListEpisodes" condition="true"> + <param name="content_path" value="plugin://plugin.video.embycon?mode=WIDGET_CONTENT&type=nextup_episodes"/> + <param name="widget_header" value="Next Up"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="4200"/> + </include> + </control> + </control> + <!-- End of Emby Widget Items --> + <control type="group" id="5000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),movies)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="movies"/> + </include> + <control type="grouplist" id="5001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="Library.HasContent(movies)"> + <param name="content_path" value="library://video/movies/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5900"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="special://skin/playlists/inprogress_movies.xsp"/> + <param name="widget_header" value="$LOCALIZE[31010]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5100"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="special://skin/playlists/recent_unwatched_movies.xsp"/> + <param name="widget_header" value="$LOCALIZE[20386]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5200"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="special://skin/playlists/unwatched_movies.xsp"/> + <param name="widget_header" value="$LOCALIZE[31007]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5300"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="special://skin/playlists/random_movies.xsp"/> + <param name="widget_header" value="$LOCALIZE[31006]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5400"/> + </include> + <include content="WidgetListCategories" condition="Library.HasContent(movies)"> + <param name="content_path" value="videodb://movies/genres/"/> + <param name="widget_header" value="$LOCALIZE[135]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="5500"/> + <param name="icon" value="$VAR[WidgetGenreIconVar]"/> + <param name="icon_height" value="70"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(movies)"> + <param name="content_path" value="videodb://movies/sets/"/> + <param name="widget_header" value="$LOCALIZE[31075]"/> + <param name="widget_target" value="videos"/> + <param name="sortby" value="random"/> + <param name="list_id" value="5600"/> + </include> + </control> + <include content="ImageWidget" condition="!Library.HasContent(movies)"> + <param name="text_label" value="$LOCALIZE[31104]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(videos,files,return)"/> + <param name="button_id" value="5500"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMovieButton)"/> + </include> + </control> + <control type="group" id="6000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),tvshows)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="tvshows"/> + </include> + <control type="grouplist" id="6001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="library://video/tvshows/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6900"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="videodb://inprogresstvshows"/> + <param name="sortby" value="lastplayed"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[626]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6100"/> + </include> + <include content="WidgetListEpisodes" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="special://skin/playlists/recent_unwatched_episodes.xsp"/> + <param name="widget_header" value="$LOCALIZE[20387]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6200"/> + </include> + <include content="WidgetListPoster" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="special://skin/playlists/unwatched_tvshows.xsp"/> + <param name="widget_header" value="$LOCALIZE[31122]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6300"/> + </include> + <include content="WidgetListCategories" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="videodb://tvshows/genres/"/> + <param name="widget_header" value="$LOCALIZE[135]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6400"/> + <param name="icon" value="$VAR[WidgetGenreIconVar]"/> + <param name="icon_height" value="70"/> + </include> + <include content="WidgetListCategories" condition="Library.HasContent(tvshows)"> + <param name="content_path" value="videodb://tvshows/studios/"/> + <param name="widget_header" value="$LOCALIZE[20388]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="6500"/> + <param name="icon" value="$VAR[WidgetStudioIconVar]"/> + <param name="icon_height" value="70"/> + </include> + </control> + <include content="ImageWidget" condition="!Library.HasContent(tvshows)"> + <param name="text_label" value="$LOCALIZE[31104]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(videos,files,return)"/> + <param name="button_id" value="6400"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoTVShowButton)"/> + </include> + </control> + <control type="group" id="7000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),music)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="music"/> + </include> + <control type="grouplist" id="7001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="Library.HasContent(music)"> + <param name="content_path" value="library://music/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7900"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="musicdb://recentlyplayedalbums"/> + <param name="widget_header" value="$LOCALIZE[517]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7100"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="musicdb://recentlyaddedalbums/"/> + <param name="widget_header" value="$LOCALIZE[359]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7200"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="special://skin/playlists/random_albums.xsp"/> + <param name="widget_header" value="$LOCALIZE[31012]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7300"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="special://skin/playlists/random_artists.xsp"/> + <param name="widget_header" value="$LOCALIZE[31013]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7400"/> + <param name="fallback_icon" value="DefaultMusicArtists.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="special://skin/playlists/unplayed_albums.xsp"/> + <param name="widget_header" value="$LOCALIZE[31014]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7500"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(music)"> + <param name="content_path" value="special://skin/playlists/mostplayed_albums.xsp"/> + <param name="widget_header" value="$LOCALIZE[31011]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="7600"/> + <param name="fallback_icon" value="DefaultMusicAlbums.png"/> + <param name="sortby" value="playcount"/> + <param name="sortorder" value="descending"/> + </include> + </control> + <include content="ImageWidget" condition="!Library.HasContent(music)"> + <param name="text_label" value="$LOCALIZE[31104]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(music,files)"/> + <param name="button_id" value="7600"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMusicButton)"/> + </include> + </control> + <control type="group" id="8000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),addons)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="addons"/> + </include> + <control type="grouplist" id="8001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories"> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="list_id" value="8900"/> + <param name="visible" value="Integer.IsGreater(Container(8100).NumItems,0) | Integer.IsGreater(Container(8200).NumItems,0) | Integer.IsGreater(Container(8300).NumItems,0) | Integer.IsGreater(Container(8400).NumItems,0) | Integer.IsGreater(Container(8500).NumItems,0)"/> + <param name="addon_submenu" value="true"/> + </include> + <include content="WidgetListSquare"> + <param name="content_path" value="addons://sources/video/"/> + <param name="widget_header" value="$LOCALIZE[1037]"/> + <param name="widget_target" value="videos"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8100"/> + <param name="fallback_icon" value="DefaultAddon.png"/> + </include> + <include content="WidgetListSquare"> + <param name="content_path" value="addons://sources/audio/"/> + <param name="widget_header" value="$LOCALIZE[1038]"/> + <param name="widget_target" value="music"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8200"/> + <param name="fallback_icon" value="DefaultAddon.png"/> + </include> + <include content="WidgetListSquare"> + <param name="content_path" value="addons://sources/executable/"/> + <param name="widget_header" value="$LOCALIZE[1043]"/> + <param name="widget_target" value="programs"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8300"/> + <param name="fallback_icon" value="DefaultAddon.png"/> + </include> + <include content="WidgetListSquare" condition="System.Platform.Android"> + <param name="content_path" value="androidapp://sources/apps/"/> + <param name="widget_header" value="$LOCALIZE[20244]"/> + <param name="widget_target" value="programs"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8400"/> + <param name="fallback_icon" value="DefaultAddon.png"/> + </include> + <include content="WidgetListSquare"> + <param name="content_path" value="addons://sources/image/"/> + <param name="widget_header" value="$LOCALIZE[1039]"/> + <param name="widget_target" value="pictures"/> + <param name="sortby" value="lastused"/> + <param name="sortorder" value="descending"/> + <param name="list_id" value="8500"/> + </include> + </control> + <include content="ImageWidget"> + <param name="text_label" value="$LOCALIZE[31119]" /> + <param name="button_label" value="$LOCALIZE[31118]" /> + <param name="button_onclick" value="ActivateWindow(addonbrowser)"/> + <param name="button_id" value="8600"/> + <param name="visible" value="!Integer.IsGreater(Container(8001).NumItems,0)"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoProgramsButton)"/> + </include> + </control> + <control type="group" id="11000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),video)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="video"/> + </include> + <control type="grouplist" id="11001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories"> + <param name="content_path" value="library://video/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="11900"/> + </include> + <include content="WidgetListCategories"> + <param name="content_path" value="sources://video/"/> + <param name="widget_header" value="$LOCALIZE[20094]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="11100"/> + </include> + <include content="WidgetListCategories"> + <param name="content_path" value="special://videoplaylists/"/> + <param name="widget_header" value="$LOCALIZE[136]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="11200"/> + <param name="icon" value="DefaultPlaylist.png"/> + </include> + </control> + <include content="ImageWidget"> + <param name="text_label" value="$LOCALIZE[31105]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(videos,root)"/> + <param name="button_id" value="11300"/> + <param name="visible" value="!Integer.IsGreater(Container(11001).NumItems,0)"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoVideosButton)"/> + </include> + </control> + <control type="group" id="12000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),livetv)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="livetv"/> + </include> + <control type="grouplist" id="12001"> + <include>WidgetGroupListCommon</include> + <control type="grouplist" id="12855"> + <height>390</height> + <left>0</left> + <right>0</right> + <top>36</top> + <orientation>horizontal</orientation> + <visible>PVR.IsRecordingTV | PVR.HasNonRecordingTVTimer</visible> + <align>center</align> + <control type="group"> + <width>674</width> + <visible>PVR.IsRecordingTV</visible> + <include content="PVRWidget"> + <param name="icon" value="$INFO[PVR.TVNowRecordingChannelIcon]" /> + <param name="header" value="$LOCALIZE[19158]" /> + <param name="label1" value="$INFO[PVR.TVNowRecordingDateTime]" /> + <param name="label2" value="$INFO[PVR.TVNowRecordingTitle][CR][COLOR=grey]$INFO[PVR.TVNowRecordingChannel][/COLOR]" /> + </include> + </control> + <control type="group"> + <width>674</width> + <visible>PVR.HasNonRecordingTVTimer</visible> + <include content="PVRWidget"> + <param name="icon" value="$INFO[PVR.TVNextRecordingChannelIcon]" /> + <param name="header" value="$LOCALIZE[19157]" /> + <param name="label1" value="$INFO[PVR.TVNextRecordingDateTime]" /> + <param name="label2" value="$INFO[PVR.TVNextRecordingTitle][CR][COLOR=grey]$INFO[PVR.TVNextRecordingChannel][/COLOR]" /> + </include> + </control> + </control> + <include content="WidgetListCategories" condition="System.HasPVRAddon"> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="list_id" value="12900"/> + <param name="pvr_submenu" value="true"/> + <param name="pvr_type" value="TV"/> + </include> + <include content="WidgetListChannels" condition="System.HasPVRAddon"> + <param name="content_path" value="pvr://channels/tv/*?view=lastplayed"/> + <param name="sortby" value="lastplayed"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[31016]"/> + <param name="widget_target" value="pvr"/> + <param name="list_id" value="12200"/> + </include> + <include content="WidgetListChannels" condition="System.HasPVRAddon"> + <param name="content_path" value="pvr://recordings/tv/active?view=flat"/> + <param name="sortby" value="date"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[31015]"/> + <param name="widget_target" value="pvr"/> + <param name="list_id" value="12300"/> + <param name="label" value="$INFO[ListItem.ChannelName]"/> + <param name="label2" value="$INFO[ListItem.Title]$INFO[ListItem.EpisodeName, (,)]"/> + </include> + </control> + <include content="ImageWidget" condition="!System.HasPVRAddon"> + <param name="text_label" value="$LOCALIZE[31143]" /> + <param name="button_label" value="$LOCALIZE[31144]" /> + <param name="button_onclick" value="ActivateWindow(addonbrowser,addons://user/xbmc.pvrclient,return)"/> + <param name="button_id" value="12400"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoTVButton)"/> + </include> + </control> + <control type="group" id="13000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),radio)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="radio"/> + </include> + <control type="grouplist" id="13001"> + <include>WidgetGroupListCommon</include> + <control type="grouplist" id="13855"> + <height>390</height> + <left>25</left> + <top>36</top> + <orientation>horizontal</orientation> + <align>right</align> + <width>1360</width> + <visible>PVR.IsRecordingRadio | PVR.HasNonRecordingRadioTimer</visible> + <control type="group"> + <width>680</width> + <visible>PVR.IsRecordingRadio</visible> + <include content="PVRWidget"> + <param name="icon" value="$INFO[PVR.RadioNowRecordingChannelIcon]" /> + <param name="header" value="$LOCALIZE[19158]" /> + <param name="label1" value="$INFO[PVR.RadioNowRecordingDateTime]" /> + <param name="label2" value="$INFO[PVR.RadioNowRecordingTitle][CR][COLOR=grey]$INFO[PVR.RadioNowRecordingChannel][/COLOR]" /> + </include> + </control> + <control type="group"> + <visible>PVR.HasNonRecordingRadioTimer</visible> + <width>680</width> + <include content="PVRWidget"> + <param name="icon" value="$INFO[PVR.RadioNextRecordingChannelIcon]" /> + <param name="header" value="$LOCALIZE[19157]" /> + <param name="label1" value="$INFO[PVR.RadioNextRecordingDateTime]" /> + <param name="label2" value="$INFO[PVR.RadioNextRecordingTitle][CR][COLOR=grey]$INFO[PVR.RadioNextRecordingChannel][/COLOR]" /> + </include> + </control> + </control> + <include content="WidgetListCategories" condition="System.HasPVRAddon"> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="list_id" value="13900"/> + <param name="pvr_submenu" value="true"/> + <param name="pvr_type" value="Radio"/> + </include> + <include content="WidgetListChannels" condition="System.HasPVRAddon"> + <param name="content_path" value="pvr://channels/radio/*?view=lastplayed"/> + <param name="sortby" value="lastplayed"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[31018]"/> + <param name="widget_target" value="files"/> + <param name="list_id" value="13200"/> + </include> + <include content="WidgetListChannels" condition="System.HasPVRAddon"> + <param name="content_path" value="pvr://recordings/radio/active?view=flat"/> + <param name="sortby" value="date"/> + <param name="sortorder" value="descending"/> + <param name="widget_header" value="$LOCALIZE[31015]"/> + <param name="widget_target" value="pvr"/> + <param name="list_id" value="13300"/> + <param name="label" value="$INFO[ListItem.ChannelName]"/> + <param name="label2" value="$INFO[ListItem.Title]$INFO[ListItem.EpisodeName, (,)]"/> + </include> + </control> + <include content="ImageWidget" condition="!System.HasPVRAddon"> + <param name="text_label" value="$LOCALIZE[31143]" /> + <param name="button_label" value="$LOCALIZE[31144]" /> + <param name="button_onclick" value="ActivateWindow(addonbrowser,addons://user/xbmc.pvrclient,return)"/> + <param name="button_id" value="13400"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoRadioButton)"/> + </include> + </control> + <control type="group" id="14000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),favorites)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="favorites"/> + </include> + <control type="panel" id="14100"> + <left>65</left> + <top>0</top> + <right>0</right> + <bottom>0</bottom> + <onleft>9000</onleft> + <onright>9000</onright> + <onup>14100</onup> + <ondown>14100</ondown> + <onclick>$INFO[ListItem.FileNameAndPath]</onclick> + <preloaditems>2</preloaditems> + <scrolltime tween="cubic" easing="out">500</scrolltime> + <orientation>vertical</orientation> + <visible>Integer.IsGreater(Container(14100).NumItems,0) | Container(14100).IsUpdating</visible> + <itemlayout width="330" height="396"> + <control type="group"> + <top>130</top> + <include content="InfoWallMusicLayout"> + <param name="fallback_image" value="DefaultFavourites.png" /> + <param name="focused" value="false" /> + </include> + </control> + </itemlayout> + <focusedlayout width="330" height="396"> + <control type="group"> + <depth>DepthContentPopout</depth> + <top>130</top> + <animation effect="zoom" start="100" end="110" time="200" tween="sine" easing="inout" center="170,320">Focus</animation> + <animation effect="zoom" start="110" end="100" time="200" tween="sine" easing="inout" center="170,320">UnFocus</animation> + <include content="InfoWallMusicLayout"> + <param name="fallback_image" value="DefaultFavourites.png" /> + <param name="focused" value="true" /> + </include> + </control> + </focusedlayout> + <content>favourites://</content> + </control> + <include content="ImageWidget"> + <param name="text_label" value="$LOCALIZE[31025]" /> + <param name="button_label" value="$LOCALIZE[31116]" /> + <param name="button_onclick" value=""/> + <param name="button_id" value="5500"/> + <param name="visible" value="!Integer.IsGreater(Container(14100).NumItems,0) + !Container(14100).IsUpdating"/> + <param name="visible_1" value="false"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoFavButton)"/> + </include> + </control> + <control type="group" id="15000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),weather)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="weather"/> + </include> + <control type="grouplist" id="15001"> + <include>WidgetGroupListCommon</include> + <control type="group" id="16678"> + <description>Weather info</description> + <left>68</left> + <right>70</right> + <top>102</top> + <height>300</height> + <visible>!String.IsEmpty(Weather.plugin)</visible> + <control type="image"> + <bottom>90</bottom> + <width>100%</width> + <texture border="21">dialogs/dialog-bg.png</texture> + </control> + <control type="label"> + <left>840</left> + <top>60</top> + <aligny>center</aligny> + <height>24</height> + <right>60</right> + <align>right</align> + <font>font30_title</font> + <label>$INFO[Weather.Location]</label> + </control> + <control type="label"> + <left>840</left> + <top>120</top> + <aligny>center</aligny> + <height>24</height> + <right>60</right> + <align>right</align> + <font>font14</font> + <label>$INFO[Weather.Conditions,, ∙ ]$INFO[Weather.Temperature]</label> + </control> + <control type="grouplist"> + <top>50</top> + <left>50</left> + <right>20</right> + <orientation>horizontal</orientation> + <align>left</align> + <itemgap>-110</itemgap> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Current.Wind)" /> + <param name="texture" value="icons/weather/wind.png" /> + <param name="header" value="404" /> + </include> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Current.Humidity)" /> + <param name="texture" value="icons/weather/humidity.png" /> + <param name="header" value="406" /> + </include> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Current.Precipitation)" /> + <param name="texture" value="icons/weather/rain.png" /> + <param name="header" value="33021" /> + </include> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Today.Sunrise)" /> + <param name="texture" value="icons/weather/sunrise.png" /> + <param name="header" value="405" /> + </include> + <include content="WeatherIcon" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="label" value="Window(weather).Property(Today.Sunset)" /> + <param name="texture" value="icons/weather/sunset.png" /> + <param name="header" value="403" /> + </include> + </control> + </control> + <include content="WeatherWidget" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="content_include" value="DailyItems" /> + <param name="list_id" value="15200" /> + <param name="widget_header" value="$LOCALIZE[31019]"/> + <param name="visible" value="!String.IsEmpty(Window(weather).Property(Daily.IsFetched))" /> + </include> + <include content="WeatherWidget" condition="!String.IsEmpty(Weather.Plugin)"> + <param name="content_include" value="HourlyItems" /> + <param name="list_id" value="15100" /> + <param name="widget_header" value="$LOCALIZE[33036]"/> + <param name="visible" value="!String.IsEmpty(Window(weather).Property(Hourly.IsFetched))" /> + </include> + </control> + <include content="ImageWidget" condition="String.IsEmpty(Weather.plugin)"> + <param name="text_label" value="$LOCALIZE[31120]" /> + <param name="button_label" value="$LOCALIZE[31121]" /> + <param name="button_onclick" value="ActivateWindow(servicesettings,weather)"/> + <param name="button_id" value="15300"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoWeatherButton)"/> + </include> + </control> + <control type="group" id="16000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),musicvideos)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="musicvideos"/> + </include> + <control type="grouplist" id="16001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="library://music/musicvideos/"/> + <param name="widget_header" value="$LOCALIZE[31148]"/> + <param name="widget_target" value="videos"/> + <param name="list_id" value="16900"/> + </include> + <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="videodb://recentlyaddedmusicvideos/"/> + <param name="widget_header" value="$LOCALIZE[20390]"/> + <param name="widget_target" value="videos"/> + <param name="main_label" value="$INFO[ListItem.Label]" /> + <param name="sub_label" value="$INFO[ListItem.Artist]" /> + <param name="thumb_label" value="$INFO[ListItem.Year]" /> + <param name="fallback_image" value="DefaultMusicSongs.png" /> + <param name="list_id" value="16300"/> + </include> + <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="special://skin/playlists/unwatched_musicvideos.xsp"/> + <param name="widget_header" value="$LOCALIZE[31151]"/> + <param name="widget_target" value="videos"/> + <param name="main_label" value="$INFO[ListItem.Label]" /> + <param name="sub_label" value="$INFO[ListItem.Artist]" /> + <param name="thumb_label" value="$INFO[ListItem.Year]" /> + <param name="fallback_image" value="DefaultMusicSongs.png" /> + <param name="list_id" value="16400"/> + </include> + <include content="WidgetListSquare" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="special://skin/playlists/random_musicvideo_artists.xsp"/> + <param name="widget_header" value="$LOCALIZE[31013]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="16200"/> + <param name="widget_limit" value="10"/> + </include> + <include content="WidgetListEpisodes" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="special://skin/playlists/random_musicvideos.xsp"/> + <param name="widget_header" value="$LOCALIZE[31152]"/> + <param name="widget_target" value="videos"/> + <param name="main_label" value="$INFO[ListItem.Label]" /> + <param name="sub_label" value="$INFO[ListItem.Artist]" /> + <param name="thumb_label" value="$INFO[ListItem.Year]" /> + <param name="fallback_image" value="DefaultMusicSongs.png" /> + <param name="list_id" value="16500"/> + </include> + <include content="WidgetListCategories" condition="Library.HasContent(musicvideos)"> + <param name="content_path" value="videodb://musicvideos/studios/"/> + <param name="widget_header" value="$LOCALIZE[20388]"/> + <param name="widget_target" value="music"/> + <param name="list_id" value="16600"/> + <param name="icon" value="$VAR[WidgetStudioIconVar]"/> + <param name="icon_height" value="70"/> + </include> + </control> + <include content="ImageWidget" condition="!Library.HasContent(musicvideos)"> + <param name="text_label" value="$LOCALIZE[31104]" /> + <param name="button_label" value="$LOCALIZE[31110]" /> + <param name="button_onclick" value="ActivateWindow(videos,files,return)"/> + <param name="button_id" value="16800"/> + <param name="button2_onclick" value="Skin.SetBool(HomeMenuNoMusicVideoButton)"/> + </include> + </control> + <control type="group" id="4000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),pictures)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="pictures"/> + </include> + <control type="grouplist" id="4001"> + <include>WidgetGroupListCommon</include> + <include content="WidgetListCategories" condition="!Skin.HasSetting(HomeMenuNoPicturesButton)"> + <param name="content_path" value="sources://pictures/"/> + <param name="widget_header" value="$LOCALIZE[20094]"/> + <param name="widget_target" value="pictures"/> + <param name="list_id" value="4100"/> + </include> + </control> + </control> + <control type="group" id="21000"> + <visible>String.IsEqual(Container(9000).ListItem.Property(id),disc)</visible> + <include content="Visible_Right_Delayed"> + <param name="id" value="disc"/> + </include> + <include content="ImageWidget"> + <param name="text_label" value="$INFO[System.DVDLabel]" /> + <param name="button_label" value="$LOCALIZE[341]" /> + <param name="button_onclick" value="PlayDisc"/> + <param name="button_id" value="21100"/> + <param name="visible" value="true"/> + <param name="button2_label" value="$LOCALIZE[13391]"/> + <param name="button2_onclick" value="EjectTray()"/> + </include> + </control> + </control> + <control type="group"> + <depth>DepthContentPanel</depth> + <include>OpenClose_Left</include> + <include content="ContentPanel"> + <param name="width" value="522" /> + </include> + <control type="fixedlist" id="9000"> + <left>0</left> + <top>240</top> + <width>462</width> + <bottom>-10</bottom> + <movement>6</movement> + <focusposition>1</focusposition> + <onfocus>ClearProperty(listposition,home)</onfocus> + <onright>SetFocus($INFO[Container(9000).ListItem.Property(menu_id)])</onright> + <onup>700</onup> + <ondown>700</ondown> + <scrolltime tween="cubic" easing="out">500</scrolltime> + <focusedlayout height="95"> + <control type="group"> + <animation effect="fade" start="100" end="0" time="0">UnFocus</animation> + <control type="image"> + <left>0</left> + <top>0</top> + <width>462</width> + <height>95</height> + <texture colordiffuse="button_focus">lists/focus.png</texture> + <animation effect="fade" start="100" end="0" time="0" condition="[!Control.HasFocus(9000) + !ControlGroup(700).HasFocus] | System.HasModalDialog">Conditional</animation> + </control> + <control type="image"> + <left>-3</left> + <top>1</top> + <width>95</width> + <height>95</height> + <texture colordiffuse="button_focus">$INFO[ListItem.Art(thumb)]</texture> + <animation effect="fade" start="0" end="100" time="300" reversible="false">Focus</animation> + </control> + <control type="image"> + <left>0</left> + <top>0</top> + <width>95</width> + <height>95</height> + <texture colordiffuse="51FFFFFF">colors/black.png</texture> + <animation effect="fade" start="100" end="0" time="0" condition="[!Control.HasFocus(9000) + !ControlGroup(700).HasFocus] | System.HasModalDialog">Conditional</animation> + </control> + </control> + <control type="image"> + <left>-3</left> + <top>1</top> + <width>95</width> + <height>95</height> + <texture>$INFO[ListItem.Art(thumb)]</texture> + </control> + <control type="label"> + <left>104</left> + <top>0</top> + <height>95</height> + <width>560</width> + <aligny>center</aligny> + <font>font37</font> + <label>$INFO[ListItem.Label]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + </focusedlayout> + <itemlayout height="95"> + <control type="image"> + <left>-3</left> + <top>1</top> + <width>95</width> + <height>95</height> + <texture colordiffuse="44FFFFFF">$INFO[ListItem.Art(thumb)]</texture> + </control> + <control type="label"> + <left>104</left> + <top>0</top> + <height>95</height> + <width>560</width> + <aligny>center</aligny> + <font>font37</font> + <label>$INFO[ListItem.Label]</label> + <shadowcolor>text_shadow</shadowcolor> + </control> + </itemlayout> + <content> + <!-- Start Emby Menu Items --> + <item> + <label>Emby Movies</label> + <onclick>RunAddon(plugin.video.embycon, /?mode=SHOW_CONTENT&item_type=Movie&media_type=movies)</onclick> + <property name="menu_id">$NUMBER[3000]</property> + <thumb>icons/sidemenu/movies.png</thumb> + <property name="id">emby_movies</property> + </item> + <item> + <label>Emby TV Shows</label> + <onclick>RunAddon(plugin.video.embycon, /?mode=SHOW_CONTENT&item_type=Series&media_type=tvshows)</onclick> + <property name="menu_id">$NUMBER[4000]</property> + <thumb>icons/sidemenu/tv.png</thumb> + <property name="id">emby_tvshows</property> + </item> + <!-- End Emby Menu Items --> + <item> + <label>$LOCALIZE[342]</label> + <onclick condition="Library.HasContent(movies)">ActivateWindow(Videos,videodb://movies/titles/,return)</onclick> + <onclick condition="!Library.HasContent(movies)">ActivateWindow(Videos,sources://video/,return)</onclick> + <property name="menu_id">$NUMBER[5000]</property> + <thumb>icons/sidemenu/movies.png</thumb> + <property name="id">movies</property> + <visible>!Skin.HasSetting(HomeMenuNoMovieButton)</visible> + </item> + <item> + <label>$LOCALIZE[20343]</label> + <onclick condition="Library.HasContent(tvshows)">ActivateWindow(Videos,videodb://tvshows/titles/,return)</onclick> + <onclick condition="!Library.HasContent(tvshows)">ActivateWindow(Videos,sources://video/,return)</onclick> + <property name="menu_id">$NUMBER[6000]</property> + <thumb>icons/sidemenu/tv.png</thumb> + <property name="id">tvshows</property> + <visible>!Skin.HasSetting(HomeMenuNoTVShowButton)</visible> + </item> + <item> + <label>$LOCALIZE[2]</label> + <onclick>ActivateWindow(Music,root,return)</onclick> + <property name="menu_id">$NUMBER[7000]</property> + <thumb>icons/sidemenu/music.png</thumb> + <property name="id">music</property> + <visible>!Skin.HasSetting(HomeMenuNoMusicButton)</visible> + </item> + <item> + <label>$LOCALIZE[427]</label> + <onclick>PlayDisc</onclick> + <property name="menu_id">$NUMBER[21000]</property> + <thumb>icons/sidemenu/disc.png</thumb> + <property name="id">disc</property> + <visible>System.HasMediaDVD</visible> + </item> + <item> + <label>$LOCALIZE[20389]</label> + <property name="menu_id">$NUMBER[16000]</property> + <onclick>ActivateWindow(Videos,musicvideos,return)</onclick> + <thumb>icons/sidemenu/musicvideos.png</thumb> + <property name="id">musicvideos</property> + <visible>!Skin.HasSetting(HomeMenuNoMusicVideoButton)</visible> + </item> + <item> + <label>$LOCALIZE[19020]</label> + <property name="menu_id">$NUMBER[12000]</property> + <onclick>ActivateWindow(TVChannels)</onclick> + <thumb>icons/sidemenu/livetv.png</thumb> + <property name="id">livetv</property> + <visible>!Skin.HasSetting(HomeMenuNoTVButton)</visible> + </item> + <item> + <label>$LOCALIZE[19021]</label> + <property name="menu_id">$NUMBER[13000]</property> + <onclick>ActivateWindow(RadioChannels)</onclick> + <thumb>icons/sidemenu/radio.png</thumb> + <property name="id">radio</property> + <visible>!Skin.HasSetting(HomeMenuNoRadioButton)</visible> + </item> + <item> + <label>$LOCALIZE[24001]</label> + <property name="menu_id">$NUMBER[8000]</property> + <onclick>ActivateWindow(1100)</onclick> + <thumb>icons/sidemenu/addons.png</thumb> + <property name="id">addons</property> + <visible>!Skin.HasSetting(HomeMenuNoProgramsButton)</visible> + </item> + <item> + <label>$LOCALIZE[1]</label> + <onclick>ActivateWindow(Pictures)</onclick> + <property name="menu_id">$NUMBER[4000]</property> + <thumb>icons/sidemenu/pictures.png</thumb> + <property name="id">pictures</property> + <visible>!Skin.HasSetting(HomeMenuNoPicturesButton)</visible> + </item> + <item> + <label>$LOCALIZE[3]</label> + <onclick>ActivateWindow(Videos,root)</onclick> + <property name="menu_id">$NUMBER[11000]</property> + <thumb>icons/sidemenu/videos.png</thumb> + <property name="id">video</property> + <visible>!Skin.HasSetting(HomeMenuNoVideosButton)</visible> + </item> + <item> + <label>$LOCALIZE[10134]</label> + <onclick>ActivateWindow(favourites)</onclick> + <property name="menu_id">$NUMBER[14000]</property> + <thumb>icons/sidemenu/favourites.png</thumb> + <property name="id">favorites</property> + <visible>!Skin.HasSetting(HomeMenuNoFavButton)</visible> + </item> + <item> + <label>$LOCALIZE[8]</label> + <onclick condition="!String.IsEmpty(Weather.Plugin)">ActivateWindow(Weather)</onclick> + <onclick condition="String.IsEmpty(Weather.Plugin)">ReplaceWindow(servicesettings,weather)</onclick> + <property name="menu_id">$NUMBER[15000]</property> + <thumb>icons/sidemenu/weather.png</thumb> + <property name="id">weather</property> + <visible>!Skin.HasSetting(HomeMenuNoWeatherButton)</visible> + </item> + </content> + </control> + <control type="grouplist" id="700"> + <orientation>horizontal</orientation> + <itemgap>0</itemgap> + <left>-8</left> + <width>480</width> + <height>110</height> + <top>100</top> + <onup>SetFocus(9000)</onup> + <onup>PageDown</onup> + <onup>PageDown</onup> + <ondown>SetFocus(9000)</ondown> + <ondown>PageUp</ondown> + <ondown>PageUp</ondown> + <onright>2000</onright> + <align>justify</align> + <include content="IconButton"> + <param name="control_id" value="804" /> + <param name="onclick" value="ActivateWindow(shutdownmenu)" /> + <param name="icon" value="icons/power.png" /> + <param name="label" value="$LOCALIZE[33060]" /> + </include> + <include content="IconButton"> + <param name="control_id" value="802" /> + <param name="onclick" value="ActivateWindow(settings)" /> + <param name="icon" value="icons/settings.png" /> + <param name="label" value="$LOCALIZE[21417]" /> + </include> + <include content="IconButton"> + <param name="control_id" value="801" /> + <param name="onclick" value="ActivateWindow(1107)" /> + <param name="icon" value="icons/search.png" /> + <param name="label" value="$LOCALIZE[137]" /> + </include> + <include content="IconButton"> + <param name="control_id" value="803" /> + <param name="onclick" value="Fullscreen" /> + <param name="icon" value="icons/now-playing/fullscreen.png" /> + <param name="label" value="$LOCALIZE[31000]" /> + <param name="visible" value="Player.HasMedia" /> + </include> + </control> + </control> + <include>BottomBar</include> + <include content="TopBar"> + <param name="breadcrumbs_label" value="" /> + </include> + <control type="group"> + <depth>DepthBars</depth> + <animation effect="slide" end="0,-90" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation> + <animation effect="fade" start="0" end="100" time="300">WindowOpen</animation> + <animation effect="fade" start="100" end="0" time="200">WindowClose</animation> + <top>30</top> + <left>90</left> + <control type="image"> + <aspectratio>keep</aspectratio> + <width>56</width> + <height>56</height> + <texture colordiffuse="button_focus">icons/logo.png</texture> + </control> + <control type="image"> + <left>40</left> + <top>10</top> + <aspectratio>keep</aspectratio> + <width>192</width> + <height>36</height> + <texture>icons/logo-text.png</texture> + </control> + </control> + <control type="rss"> + <animation effect="slide" end="0,90" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation> + <animation effect="fade" start="0" end="100" time="400">WindowOpen</animation> + <animation effect="fade" start="100" end="0" time="300">WindowClose</animation> + <left>0</left> + <bottom>0</bottom> + <height>39</height> + <width>100%</width> + <font>font12</font> + <urlset>1</urlset> + <hitrect x="-100" y="0" w="1" h="1" /> + <titlecolor>button_focus</titlecolor> + <textcolor>button_focus</textcolor> + <shadowcolor>text_shadow</shadowcolor> + <headlinecolor>FFC0C0C0</headlinecolor> + </control> + </control> + </controls> +</window> diff --git a/plugin.video.embycon/service.py b/plugin.video.embycon/service.py new file mode 100644 index 0000000..eb38b02 --- /dev/null +++ b/plugin.video.embycon/service.py @@ -0,0 +1,324 @@ +# coding=utf-8 +# Gnu General Public License - see LICENSE.TXT + +import xbmc +import xbmcaddon +import xbmcgui +import time +import json + +from resources.lib.downloadutils import DownloadUtils +from resources.lib.simple_logging import SimpleLogging +from resources.lib.play_utils import playFile +from resources.lib.kodi_utils import HomeWindow +from resources.lib.translation import i18n + +# clear user and token when logging in +home_window = HomeWindow() +home_window.clearProperty("userid") +home_window.clearProperty("AccessToken") +home_window.clearProperty("Params") + +log = SimpleLogging('service') +download_utils = DownloadUtils() + +# auth the service +try: + download_utils.authenticate() +except Exception, e: + pass + + +def hasData(data): + if data is None or len(data) == 0 or data == "None": + return False + else: + return True + + +def sendProgress(): + playing_file = xbmc.Player().getPlayingFile() + play_data = monitor.played_information.get(playing_file) + + if play_data is None: + return + + log.debug("Sending Progress Update") + + play_time = xbmc.Player().getTime() + play_data["currentPossition"] = play_time + + item_id = play_data.get("item_id") + if item_id is None: + return + + ticks = int(play_time * 10000000) + paused = play_data.get("paused", False) + playback_type = play_data.get("playback_type") + + postdata = { + 'QueueableMediaTypes': "Video", + 'CanSeek': True, + 'ItemId': item_id, + 'MediaSourceId': item_id, + 'PositionTicks': ticks, + 'IsPaused': paused, + 'IsMuted': False, + 'PlayMethod': playback_type + } + + log.debug("Sending POST progress started: %s." % postdata) + + url = "{server}/emby/Sessions/Playing/Progress" + download_utils.downloadUrl(url, postBody=postdata, method="POST") + +def promptForStopActions(item_id, current_possition): + + settings = xbmcaddon.Addon(id='plugin.video.embycon') + + prompt_next_percentage = int(settings.getSetting('promptPlayNextEpisodePercentage')) + prompt_delete_episode_percentage = int(settings.getSetting('promptDeleteEpisodePercentage')) + prompt_delete_movie_percentage = int(settings.getSetting('promptDeleteMoviePercentage')) + + # everything is off so return + if prompt_next_percentage == 100 and prompt_delete_episode_percentage == 100 and prompt_delete_movie_percentage == 100: + return + + jsonData = download_utils.downloadUrl("{server}/emby/Users/{userid}/Items/" + + item_id + "?format=json", + suppress=False, popup=1) + result = json.loads(jsonData) + prompt_to_delete = False + runtime = result.get("RunTimeTicks", 0) + + # if no runtime we cant calculate perceantge so just return + if runtime == 0: + log.debug("No runtime so returing") + return + + # item percentage complete + percenatge_complete = int(((current_possition * 10000000) / runtime) * 100) + log.debug("Episode Percentage Complete: %s" % percenatge_complete) + + if (prompt_delete_episode_percentage < 100 and + result.get("Type", "na") == "Episode" and + percenatge_complete > prompt_delete_episode_percentage): + prompt_to_delete = True + + if (prompt_delete_movie_percentage < 100 and + result.get("Type", "na") == "Movie" and + percenatge_complete > prompt_delete_movie_percentage): + prompt_to_delete = True + + if prompt_to_delete: + log.debug("Prompting for delete") + resp = xbmcgui.Dialog().yesno(i18n('confirm_file_delete'), i18n('file_delete_confirm'), autoclose=10000) + if resp: + log.debug("Deleting item: %s" % item_id) + url = "{server}/emby/Items/%s?format=json" % item_id + download_utils.downloadUrl(url, method="DELETE") + xbmc.executebuiltin("Container.Refresh") + + # prompt for next episode + if (prompt_next_percentage < 100 and + result.get("Type", "na") == "Episode" and + percenatge_complete > prompt_next_percentage): + parendId = result.get("ParentId", "na") + item_index = result.get("IndexNumber", -1) + + if parendId == "na": + log.debug("No parent id, can not prompt for next episode") + return + + if item_index == -1: + log.debug("No episode number, can not prompt for next episode") + return + + jsonData = download_utils.downloadUrl('{server}/emby/Users/{userid}/Items?' + + '?Recursive=true' + + '&ParentId=' + parendId + + #'&Filters=IsUnplayed,IsNotFolder' + + '&IsVirtualUnaired=false' + + '&IsMissing=False' + + '&IncludeItemTypes=Episode' + + '&ImageTypeLimit=1' + + '&format=json', + suppress=False, popup=1) + + items_result = json.loads(jsonData) + log.debug("Prompt Next Item Details: %s" % items_result) + # find next episode + item_list = items_result.get("Items", []) + for item in item_list: + index = item.get("IndexNumber", -1) + if index > item_index: # find the next episode in the season + resp = xbmcgui.Dialog().yesno(i18n("play_next_title"), i18n("play_next_question"), autoclose=10000) + if resp: + next_item_id = item.get("Id") + log.debug("Playing Next Episode: %s" % next_item_id) + + play_info = {} + play_info["item_id"] = next_item_id + play_info["auto_resume"] = "-1" + play_info["force_transcode"] = False + play_data = json.dumps(play_info) + + home_window = HomeWindow() + home_window.setProperty("item_id", next_item_id) + home_window.setProperty("play_item_message", play_data) + + break + + +def stopAll(played_information): + if len(played_information) == 0: + return + + log.debug("played_information : " + str(played_information)) + + for item_url in played_information: + data = played_information.get(item_url) + if data is not None: + log.debug("item_url : " + item_url) + log.debug("item_data : " + str(data)) + + current_possition = data.get("currentPossition", 0) + emby_item_id = data.get("item_id") + + if hasData(emby_item_id): + log.debug("Playback Stopped at: " + str(int(current_possition * 10000000))) + + url = "{server}/emby/Sessions/Playing/Stopped" + postdata = { + 'ItemId': emby_item_id, + 'MediaSourceId': emby_item_id, + 'PositionTicks': int(current_possition * 10000000) + } + download_utils.downloadUrl(url, postBody=postdata, method="POST") + + promptForStopActions(emby_item_id, current_possition) + + played_information.clear() + + +class Service(xbmc.Player): + played_information = {} + + def __init__(self, *args): + log.debug("Starting monitor service: " + str(args)) + self.played_information = {} + + def onPlayBackStarted(self): + # Will be called when xbmc starts playing a file + stopAll(self.played_information) + + current_playing_file = xbmc.Player().getPlayingFile() + log.debug("onPlayBackStarted: " + current_playing_file) + + home_window = HomeWindow() + emby_item_id = home_window.getProperty("item_id") + playback_type = home_window.getProperty("PlaybackType_" + emby_item_id) + + # if we could not find the ID of the current item then return + if emby_item_id is None or len(emby_item_id) == 0: + return + + log.debug("Sending Playback Started") + postdata = { + 'QueueableMediaTypes': "Video", + 'CanSeek': True, + 'ItemId': emby_item_id, + 'MediaSourceId': emby_item_id, + 'PlayMethod': playback_type + } + + log.debug("Sending POST play started: %s." % postdata) + + url = "{server}/emby/Sessions/Playing" + download_utils.downloadUrl(url, postBody=postdata, method="POST") + + data = {} + data["item_id"] = emby_item_id + data["paused"] = False + data["playback_type"] = playback_type + self.played_information[current_playing_file] = data + + log.debug("ADDING_FILE : " + current_playing_file) + log.debug("ADDING_FILE : " + str(self.played_information)) + + def onPlayBackEnded(self): + # Will be called when kodi stops playing a file + log.debug("EmbyCon Service -> onPlayBackEnded") + home_window = HomeWindow() + home_window.clearProperty("item_id") + stopAll(self.played_information) + + def onPlayBackStopped(self): + # Will be called when user stops kodi playing a file + log.debug("onPlayBackStopped") + home_window = HomeWindow() + home_window.clearProperty("item_id") + stopAll(self.played_information) + + def onPlayBackPaused(self): + # Will be called when kodi pauses the video + log.debug("onPlayBackPaused") + current_file = xbmc.Player().getPlayingFile() + play_data = monitor.played_information.get(current_file) + + if play_data is not None: + play_data['paused'] = True + sendProgress() + + def onPlayBackResumed(self): + # Will be called when kodi resumes the video + log.debug("onPlayBackResumed") + current_file = xbmc.Player().getPlayingFile() + play_data = monitor.played_information.get(current_file) + + if play_data is not None: + play_data['paused'] = False + sendProgress() + + def onPlayBackSeek(self, time, seekOffset): + # Will be called when kodi seeks in video + log.debug("onPlayBackSeek") + sendProgress() + + +monitor = Service() +last_progress_update = time.time() + +xbmc_monitor = xbmc.Monitor() +while not xbmc_monitor.abortRequested(): + + home_window = HomeWindow() + + if xbmc.Player().isPlaying(): + + try: + if (time.time() - last_progress_update) > 10: + last_progress_update = time.time() + sendProgress() + + except Exception as error: + log.error("Exception in Playback Monitor : " + str(error)) + + else: + play_data = home_window.getProperty("play_item_message") + if play_data: + home_window.clearProperty("play_item_message") + play_info = json.loads(play_data) + playFile(play_info) + + xbmc_monitor.waitForAbort(1) + HomeWindow().setProperty("Service_Timestamp", str(int(time.time()))) + + +# clear user and token when loggin off +home_window = HomeWindow() +home_window.clearProperty("userid") +home_window.clearProperty("AccessToken") +home_window.clearProperty("Params") + +log.debug("Service shutting down") |