summaryrefslogtreecommitdiff
path: root/plugin.video.embycon
diff options
context:
space:
mode:
authorShaun <shaun@bluebit.com.au>2017-07-16 00:09:51 +1000
committerenen92 <enen92@users.noreply.github.com>2017-07-15 15:09:51 +0100
commit49d6dd033fc083c4ee6f69947861c0d923144041 (patch)
tree541668912f24621431778016b2ad3ccb4ad08698 /plugin.video.embycon
parent09c5e3fe8977c5ffaf6599b3c6a35405a8a141ca (diff)
[plugin.video.embycon] 1.3.17 (#1309)
[plugin.video.embycon] 1.3.17
Diffstat (limited to 'plugin.video.embycon')
-rw-r--r--plugin.video.embycon/LICENSE.txt283
-rw-r--r--plugin.video.embycon/addon.xml24
-rw-r--r--plugin.video.embycon/default.py13
-rw-r--r--plugin.video.embycon/fanart.jpgbin0 -> 37645 bytes
-rw-r--r--plugin.video.embycon/icon.pngbin0 -> 30138 bytes
-rw-r--r--plugin.video.embycon/resources/__init__.py1
-rw-r--r--plugin.video.embycon/resources/language/resource.language.en_gb/strings.po544
-rw-r--r--plugin.video.embycon/resources/lib/__init__.py1
-rw-r--r--plugin.video.embycon/resources/lib/clientinfo.py46
-rw-r--r--plugin.video.embycon/resources/lib/datamanager.py159
-rw-r--r--plugin.video.embycon/resources/lib/downloadutils.py401
-rw-r--r--plugin.video.embycon/resources/lib/functions.py1332
-rw-r--r--plugin.video.embycon/resources/lib/json_rpc.py32
-rw-r--r--plugin.video.embycon/resources/lib/kodi_utils.py67
-rw-r--r--plugin.video.embycon/resources/lib/menu_functions.py510
-rw-r--r--plugin.video.embycon/resources/lib/play_utils.py173
-rw-r--r--plugin.video.embycon/resources/lib/resume_dialog.py43
-rw-r--r--plugin.video.embycon/resources/lib/server_detect.py185
-rw-r--r--plugin.video.embycon/resources/lib/server_sessions.py65
-rw-r--r--plugin.video.embycon/resources/lib/simple_logging.py39
-rw-r--r--plugin.video.embycon/resources/lib/translation.py98
-rw-r--r--plugin.video.embycon/resources/lib/utils.py191
-rw-r--r--plugin.video.embycon/resources/settings.xml55
-rw-r--r--plugin.video.embycon/resources/skins/default/720p/ResumeDialog.xml50
-rw-r--r--plugin.video.embycon/resources/skins/default/media/bg.pngbin0 -> 312 bytes
-rw-r--r--plugin.video.embycon/resources/skins/default/media/white.pngbin0 -> 263 bytes
-rw-r--r--plugin.video.embycon/resources/skins/skin.estuary/copy_home.txt17
-rw-r--r--plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.1.xml1061
-rw-r--r--plugin.video.embycon/resources/skins/skin.estuary/xml/Home-17.3.xml1059
-rw-r--r--plugin.video.embycon/service.py324
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
new file mode 100644
index 0000000..4c705f6
--- /dev/null
+++ b/plugin.video.embycon/fanart.jpg
Binary files differ
diff --git a/plugin.video.embycon/icon.png b/plugin.video.embycon/icon.png
new file mode 100644
index 0000000..f399123
--- /dev/null
+++ b/plugin.video.embycon/icon.png
Binary files differ
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="&lt;none&gt;" 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
new file mode 100644
index 0000000..68d7e61
--- /dev/null
+++ b/plugin.video.embycon/resources/skins/default/media/bg.png
Binary files differ
diff --git a/plugin.video.embycon/resources/skins/default/media/white.png b/plugin.video.embycon/resources/skins/default/media/white.png
new file mode 100644
index 0000000..136602c
--- /dev/null
+++ b/plugin.video.embycon/resources/skins/default/media/white.png
Binary files differ
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&amp;item_type=Movie&amp;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&amp;item_type=Series&amp;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&amp;item_type=Movie&amp;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&amp;item_type=Series&amp;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")