mirror of
https://github.com/jellyfin/jellycon.git
synced 2024-11-26 15:50:41 +00:00
new addon based on the XBMC addon
This commit is contained in:
commit
d348f04159
283
LICENSE.txt
Normal file
283
LICENSE.txt
Normal file
@ -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
|
||||
-------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------
|
27
addon.xml
Normal file
27
addon.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.xbmb3c"
|
||||
name="XBMB3C"
|
||||
version="0.9.546"
|
||||
provider-name="xnappo, null_pointer, im85288">
|
||||
<requires>
|
||||
<import addon="script.module.requests" version="1.1.0" />
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.simplejson" version="2.0.10"/>
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource"
|
||||
library="default.py">
|
||||
<provides>executable video audio image</provides>
|
||||
</extension>
|
||||
<extension point="xbmc.service" library="service.py" start="startup">
|
||||
</extension>
|
||||
<extension point="xbmc.addon.metadata">
|
||||
<platform>all</platform>
|
||||
<language>en</language>
|
||||
<license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license>
|
||||
<forum>http://mediabrowser.tv/community/index.php?/forum/99-xbmb3c/</forum>
|
||||
<website>http://mediabrowser.tv/</website>
|
||||
<source>https://github.com/MediaBrowser/MediaBrowser.XBMC/</source>
|
||||
<summary lang="en">Browse and play local video, music and photo media file managed by the Media Browser Server</summary>
|
||||
<description lang="en">Browse and play local video, music and photo media file managed by the Media Browser Server</description>
|
||||
</extension>
|
||||
</addon>
|
163
changelog.txt
Normal file
163
changelog.txt
Normal file
@ -0,0 +1,163 @@
|
||||
0.9.6
|
||||
Features:
|
||||
- Added MB3 Channels
|
||||
- Added custom Item Info dialog
|
||||
- Added a lot of new variables for use by skinners
|
||||
- Changed behaviour of 'auto enter single season' - faster performance
|
||||
- Added cache for 'All Movies' node.
|
||||
- Added new 'search' dialog
|
||||
- Added option to use 'poster' indicators for watched/in progress etc.
|
||||
- Added option to sort 'Next Up' by show name.
|
||||
- Option to disable CoverArt
|
||||
- Various performance enhancements
|
||||
- Added transcoding functionality
|
||||
- Dutch language support
|
||||
- Added unwatches movies/episodes nodes
|
||||
- Added Genres, Studio, Actor nodes
|
||||
- Added SuggestedItems nodes
|
||||
- Partial implementation of new MB3 security
|
||||
- Added flatten seasons option
|
||||
- Support MusicVideos/HomeVideos in Recently Added
|
||||
- Backdrops for user defines collections
|
||||
- Support for 'media stubs' (physical media etc).
|
||||
Bug fixes:
|
||||
- Fix long XBMC close times
|
||||
- Fixed incorrectly setting 'watched' under certain conditions
|
||||
- Fixed rotating art for episodes
|
||||
- Fixed percentage complete calculation
|
||||
- More ArtWork fixes
|
||||
- Multiple 'NoneType' fixes
|
||||
- Don't offer 'Play from here' on folders
|
||||
- Fix 'ItemInfo' 'Play' button (requires skin mod)
|
||||
- Consider 'SortName' for collections
|
||||
|
||||
0.9.5
|
||||
- Auto server discovery
|
||||
- Cast Images
|
||||
- Actor cross-referencing
|
||||
- Additional artwork and metadata during playback
|
||||
- Rotating backdrops for individual items
|
||||
- Default views settings (must be supported by the skin)
|
||||
- TV Theme music support
|
||||
- Option to disable background services
|
||||
- Proper artwork support at the Series/Season/Episode levels
|
||||
- New method for skin widget support
|
||||
- Proper sorting for Series and BoxSets
|
||||
- Present episode counts and watch/unwatched counts
|
||||
- Removed skin_diffs folder - use XBMB3C skin repo instead
|
||||
|
||||
0.9.0
|
||||
- Search capability
|
||||
- Better skin integration
|
||||
- Added Favorite Shows node
|
||||
- Update NextUp widget on stop
|
||||
- Check that service is running
|
||||
- Error handling for non-UNC paths
|
||||
- Photo widget support
|
||||
- Better MB3->XBMC art mapping
|
||||
- Meta-data for playback in-progress items
|
||||
- More granular debug logs
|
||||
- No meta-data cache for less than 25 items (helps with TV status updates)
|
||||
|
||||
0.8.5
|
||||
- Added remote control from other clients (null_pointer)
|
||||
- Added trailer support (im85288)
|
||||
- Added Couch Potato trailer integration (im85288)
|
||||
- Updated to support server security update (xnappo)
|
||||
- Server path substitution support (im85288)
|
||||
- Added premier date and airtime to Upcoming TV (im85288)
|
||||
- Added in-progress Movie and Episode entry points (im85288)
|
||||
- Added percent text to in-progress items (null_pointer)
|
||||
- Aeon Nox widget mods (Recently Added Moves/Episodes, NextUp Episodes) (xnappo)
|
||||
- Added offer delete on episode played option (xnappo)
|
||||
- Added optional progress dialog for large collections (null_pointer)
|
||||
- Various improvements to data presentation (all)
|
||||
|
||||
0.8.0 - Improved cache accuracy (null_pointer)
|
||||
- Added Confluence auto-menu creation
|
||||
(add movies, then TV, then others to favorites, relaunch) (null_pointer)
|
||||
- Added hooks for xperience1080++ automation. Gotham only! (im85288)
|
||||
- Added much more art (disc art, clear art banner art etc). Gotham only! (im85288)
|
||||
- Added total play time for boxsets (null_pointer)
|
||||
- Provide skins boxset information (im85288)
|
||||
- Added rotating background fanart (null_pointer/im85288)
|
||||
- Use GZIP for JSON requests (null_pointer)
|
||||
- Added configurable options for played and resume times/percentages (null_pointer)
|
||||
- Added extra information for RecentMovies/Episodes (im85288)
|
||||
- Added RecentAlbums, RandomMovies, RandomEpisodes, RandomAlbums, NextUpTV services (im85288)
|
||||
- Provide runtime and other information in list view (xnappo)
|
||||
- Added 'Studio' metadata (xnappo)
|
||||
- Added 'poster' art (xnappo)
|
||||
- Added BoxSet video node (xnappo)
|
||||
- Added trailers count, fixed movie totals (im85288)
|
||||
|
||||
0.7.5 - Added simplejson/json switch
|
||||
- Added simplejson as a requirement
|
||||
- Changed to use 'Type' instead of 'DisplayMediaType' per Luke
|
||||
- Added Confluence skin mods (null_pointer)
|
||||
- Added recentmovie/recenttv list for use by skins (null_pointer)
|
||||
- Bug fix in service to use data from settings
|
||||
- Make using Series art for episodes an option
|
||||
|
||||
0.7.0 - Switched all data from XML to JSON
|
||||
- NOTE: If you have added nodes to your main menu, you will need to redo them
|
||||
- Removed local image copying - new image proxy service by Null_Pointer!
|
||||
- NOTE: You can delete the .png files in addon_data!
|
||||
- Added local data cache (null_pointer)
|
||||
- Changed 'Play All From Here' to start from current episode
|
||||
- Fixed crash in latest episodes when a 'special' is present
|
||||
- Fixed DVD playback
|
||||
|
||||
0.6.5 - Added preliminary transcoding support
|
||||
- Added preliminary music support (plays, no metadata yet)
|
||||
- Fixed bug with non-ASCII characters in collection name
|
||||
- Gracefully handle username not specified
|
||||
- Fixed XML compliance issue for official repo submission
|
||||
|
||||
0.6.0 - Added resume tracking
|
||||
- Added playback from resume point (SMB only)
|
||||
- Added support for multiple users
|
||||
- Added password authentication
|
||||
- Added SMB username/password option
|
||||
- Added option to play from HTTP instead of SMB (note: resume does not work with this option)
|
||||
- Added default sort modes
|
||||
- Changed to not resolve real path until playback. Pi speedup?
|
||||
- Fixed boxsets containing only one movie
|
||||
- Removed xml caching - not needed (switched from httplib2 to requests)
|
||||
- Cleaned up more for official repo submission requirements
|
||||
|
||||
0.5.5 - Finished requirements for official repo submission
|
||||
- Added localization
|
||||
- Added 'Auto enter single folder items' option
|
||||
- Added 'Play from here'
|
||||
- Added Genre filter to context menu
|
||||
- Added 'NextUp' menu entry
|
||||
|
||||
0.5.0 - Added Sorting support via Context Menu
|
||||
- Added Sort order support via Context Menu
|
||||
- Fixed bug with unaired shows appearing in TV
|
||||
- Fixed bug with certain characters causing errors in playback path
|
||||
|
||||
0.4.5 - Added Recently Added Movies, TV
|
||||
- Added Favorites support (excuse the trophy icon instead of heart, best I could do)
|
||||
- Added Upcoming TV
|
||||
- Added option to mark watched on play (still not progress tracking)
|
||||
- Preparing for official repository submission (dos2unix lfs)
|
||||
- Made context menu smarter
|
||||
- Use Show art for Episodes (for now - MB3 episode artwork doesn't play well will XBMC skins)
|
||||
- Changed cache to default to 0 (off) - this was needed only because of a FlexRaid issue on my system
|
||||
|
||||
0.4.0 - Added section title
|
||||
- Display correct list type for category
|
||||
- Implemented context menus for delete/mark watched/mark unwatched.
|
||||
- Added episode numbers
|
||||
- Added cast info
|
||||
|
||||
0.3.0 - Fixes boxsets
|
||||
- Added meta-data
|
||||
|
||||
0.2.0 - Added caching
|
||||
- Removed more plex stuff
|
||||
- XBMB3C-specific settings
|
||||
|
||||
0.1.0 - Initial release
|
2897
default.py
Normal file
2897
default.py
Normal file
File diff suppressed because it is too large
Load Diff
1
resources/__init__.py
Normal file
1
resources/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Dummy file to make this directory a package.
|
194
resources/language/English/strings.xml
Normal file
194
resources/language/English/strings.xml
Normal file
@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<strings>
|
||||
<string id="30000">Primary Server Address</string>
|
||||
<string id="30001">Auto enter single folder items:</string>
|
||||
<string id="30002">Play from HTTP instead of SMB:</string>
|
||||
<string id="30004">Log Level:</string>
|
||||
<string id="30005">Username: </string>
|
||||
<string id="30006">Password: </string>
|
||||
<string id="30007">Samba Username: </string>
|
||||
<string id="30008">Samba Password: </string>
|
||||
<string id="30009">Transcode: </string>
|
||||
<string id="30010">Enable performance profiling</string>
|
||||
|
||||
<string id="30014">MediaBrowser</string>
|
||||
<string id="30015">Network</string>
|
||||
<string id="30016">Device Name</string>
|
||||
|
||||
<string id="30022">Advanced</string>
|
||||
<string id="30024">Username:</string>
|
||||
<string id="30025">Password:</string>
|
||||
<string id="30026">Use SIMPLEJSON instead of JSON</string>
|
||||
|
||||
<string id="30030">Port Number:</string>
|
||||
<string id="30036">Number of recent Movies to show:</string>
|
||||
<string id="30037">Number of recent TV episodes to show:</string>
|
||||
<string id="30035">Number of recent Music Albums to show:</string>
|
||||
<string id="30038">Mark watched at start of playback:</string>
|
||||
<string id="30039">Set Season poster for episodes</string>
|
||||
|
||||
<string id="30040">Genre Filter ...</string>
|
||||
<string id="30041">Play All from Here</string>
|
||||
<string id="30042">Refresh</string>
|
||||
<string id="30043">Delete</string>
|
||||
<string id="30046">Add Movie to CouchPotato</string>
|
||||
|
||||
<string id="30044">Incorrect Username/Password</string>
|
||||
<string id="30045">Username not found</string>
|
||||
|
||||
<string id="30052">Deleting</string>
|
||||
<string id="30053">Waiting for server to delete</string>
|
||||
|
||||
<string id="30059">Server Default</string>
|
||||
<string id="30060">Title</string>
|
||||
<string id="30061">Year</string>
|
||||
<string id="30062">Premiere Date</string>
|
||||
<string id="30063">Date Created</string>
|
||||
<string id="30064">Critic Rating</string>
|
||||
<string id="30065">Community Rating</string>
|
||||
<string id="30066">Play Count</string>
|
||||
<string id="30067">Budget</string>
|
||||
|
||||
<string id="30068">Sort By</string>
|
||||
|
||||
<string id="30069">None</string>
|
||||
<string id="30070">Action</string>
|
||||
<string id="30071">Adventure</string>
|
||||
<string id="30072">Animation</string>
|
||||
<string id="30073">Crime</string>
|
||||
<string id="30074">Comedy</string>
|
||||
<string id="30075">Documentary</string>
|
||||
<string id="30076">Drama</string>
|
||||
<string id="30077">Fantasy</string>
|
||||
<string id="30078">Foreign</string>
|
||||
<string id="30079">History</string>
|
||||
<string id="30080">Horror</string>
|
||||
<string id="30081">Music</string>
|
||||
<string id="30082">Musical</string>
|
||||
<string id="30083">Mystery</string>
|
||||
<string id="30084">Romance</string>
|
||||
<string id="30085">Science Fiction</string>
|
||||
<string id="30086">Short</string>
|
||||
<string id="30087">Suspense</string>
|
||||
<string id="30088">Thriller</string>
|
||||
<string id="30089">Western</string>
|
||||
|
||||
<string id="30090">Genre Filter</string>
|
||||
<string id="30091">Confirm file delete?</string>
|
||||
<string id="30092">Delete this item? This action will delete media and associated data files.</string>
|
||||
|
||||
<string id="30093">Mark Watched</string>
|
||||
<string id="30094">Mark Unwatched</string>
|
||||
<string id="30095">Add to Favorites</string>
|
||||
<string id="30096">Remove from Favorites</string>
|
||||
<string id="30097">Sort By ...</string>
|
||||
<string id="30098">Sort Order Descending</string>
|
||||
<string id="30099">Sort Order Ascending</string>
|
||||
<string id="30100">Show People</string>
|
||||
|
||||
<string id="30110">Interface</string>
|
||||
<string id="30111">Include Stream Info</string>
|
||||
<string id="30112">Include People</string>
|
||||
<string id="30113">Include Overview</string>
|
||||
<string id="30114">On Resume Jump Back Seconds</string>
|
||||
<string id="30115">Mark Played When Stopping Above %</string>
|
||||
<string id="30116">Add Item and Played Counts</string>
|
||||
<string id="30117"> - Background Art Refresh Rate (seconds)</string>
|
||||
<string id="30118">Add Resume Percent</string>
|
||||
<string id="30119">Add Episode Number</string>
|
||||
<string id="30120">Show Load Progress</string>
|
||||
<string id="30121">Loading Content</string>
|
||||
<string id="30122">Retrieving Data</string>
|
||||
<string id="30123">Parsing Jason Data</string>
|
||||
<string id="30124">Downloading Jason Data</string>
|
||||
<string id="30125">Done</string>
|
||||
<string id="30126">Processing Item : </string>
|
||||
<string id="30127">Offer delete for watched episodes</string>
|
||||
<string id="30128">Play Error</string>
|
||||
<string id="30129">This item is not playable</string>
|
||||
<string id="30130">Local path detected</string>
|
||||
<string id="30131">Your MB3 Server contains local paths. Please change server paths to UNC or change XBMB3C setting 'Play from Stream' to true. Path: </string>
|
||||
<string id="30132">Warning</string>
|
||||
<string id="30133">Debug logging enabled.</string>
|
||||
<string id="30134">This will affect performance.</string>
|
||||
<string id="30135">Error</string>
|
||||
<string id="30136">XBMB3C service is not running</string>
|
||||
<string id="30137">Please restart XBMC</string>
|
||||
<string id="30138">Search</string>
|
||||
|
||||
<string id="30139">Enable Theme Music (Requires Restart)</string>
|
||||
<string id="30140"> - Loop Theme Music</string>
|
||||
<string id="30141">Enable Background Image (Requires Restart)</string>
|
||||
<string id="30142">Services</string>
|
||||
<string id="30143">Enable Info Loader (Requires Restart)</string>
|
||||
<string id="30144">Enable Menu Loader (Requires Restart)</string>
|
||||
<string id="30145">Enable WebSocket Remote (Requires Restart)</string>
|
||||
<string id="30146">Enable In Progress Loader (Requires Restart)</string>
|
||||
<string id="30147">Enable Recent Info Loader (Requires Restart)</string>
|
||||
<string id="30148">Enable Random Loader (Requires Restart)</string>
|
||||
<string id="30149">Enable Next Up Loader (Requires Restart)</string>
|
||||
|
||||
<string id="30150">Skin does not support setting views</string>
|
||||
<string id="30151">Select item action (Requires Restart)</string>
|
||||
|
||||
<string id="30152">Show Indicators</string>
|
||||
<string id="30153"> - Show Watched Indicator</string>
|
||||
<string id="30154"> - Show Unplayed Count Indicator</string>
|
||||
<string id="30155"> - Show Played Percentage Indicator</string>
|
||||
<string id="30156">Sort NextUp by Show Title</string>
|
||||
<string id="30157">Disable Enhanced Images (eg CoverArt)</string>
|
||||
<string id="30158">Metadata</string>
|
||||
<string id="30159">Artwork</string>
|
||||
<string id="30160">Video Quality</string>
|
||||
|
||||
<string id="30161">Enable Suggested Loader (Requires Restart)</string>
|
||||
<string id="30162">Add Season Number</string>
|
||||
<string id="30163">Flatten Seasons</string>
|
||||
<string id="30164">Direct Play - HTTP</string>
|
||||
<string id="30165">Direct Play</string>
|
||||
<string id="30166">Transcoding</string>
|
||||
<string id="30167">Server Detection Succeeded</string>
|
||||
<string id="30168">Found server</string>
|
||||
<string id="30169">Address : </string>
|
||||
|
||||
|
||||
<string id="30170">All Movies</string>
|
||||
<string id="30171">All TV</string>
|
||||
<string id="30172">All Music</string>
|
||||
<string id="30173">Channels</string>
|
||||
<string id="30174">Recently Added Movies</string>
|
||||
<string id="30175">Recently Added Episodes</string>
|
||||
<string id="30176">Recently Added Albums</string>
|
||||
<string id="30177">In Progress Movies</string>
|
||||
<string id="30178">In Progress Episodes</string>
|
||||
<string id="30179">Next Episodes</string>
|
||||
<string id="30180">Favorite Movies</string>
|
||||
<string id="30181">Favorite Shows</string>
|
||||
<string id="30182">Favorite Episodes</string>
|
||||
<string id="30183">Frequent Played Albums</string>
|
||||
<string id="30184">Upcoming TV</string>
|
||||
<string id="30185">BoxSets</string>
|
||||
<string id="30186">Trailers</string>
|
||||
<string id="30187">Music Videos</string>
|
||||
<string id="30188">Photos</string>
|
||||
<string id="30189">Unwatched Movies</string>
|
||||
<string id="30190">Movie Genres</string>
|
||||
<string id="30191">Movie Studios</string>
|
||||
<string id="30192">Movie Actors</string>
|
||||
<string id="30193">Unwatched Episodes</string>
|
||||
<string id="30194">TV Genres</string>
|
||||
<string id="30195">TV Networks</string>
|
||||
<string id="30196">TV Actors</string>
|
||||
<string id="30197">Playlists</string>
|
||||
<string id="30198">Search</string>
|
||||
<string id="30199">Set Views</string>
|
||||
|
||||
<string id="30200">Select User</string>
|
||||
<string id="30201">Profiling enabled.</string>
|
||||
<string id="30202">Please remember to turn off when finished testing.</string>
|
||||
<string id="30203">Error in ArtworkRotationThread</string>
|
||||
<string id="30204">Unable to connect to host</string>
|
||||
<string id="30205">Error in LoadMenuOptionsThread</string>
|
||||
|
||||
|
||||
</strings>
|
809
resources/lib/ArtworkLoader.py
Normal file
809
resources/lib/ArtworkLoader.py
Normal file
@ -0,0 +1,809 @@
|
||||
#################################################################################################
|
||||
# Start of BackgroundRotationThread
|
||||
# Sets a backgound property to a fan art link
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
import urllib2
|
||||
import random
|
||||
import time
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
class ArtworkRotationThread(threading.Thread):
|
||||
|
||||
movie_art_links = []
|
||||
tv_art_links = []
|
||||
music_art_links = []
|
||||
global_art_links = []
|
||||
item_art_links = {}
|
||||
current_movie_art = 0
|
||||
current_tv_art = 0
|
||||
current_music_art = 0
|
||||
current_global_art = 0
|
||||
current_item_art = 0
|
||||
linksLoaded = False
|
||||
logLevel = 0
|
||||
currentFilteredIndex = {}
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
self.getString = self.addonSettings.getLocalizedString
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C BackgroundRotationThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C BackgroundRotationThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.run_internal()
|
||||
except Exception, e:
|
||||
xbmcgui.Dialog().ok(self.getString(30203), str(e))
|
||||
raise
|
||||
|
||||
def run_internal(self):
|
||||
self.logMsg("Started")
|
||||
|
||||
try:
|
||||
self.loadLastBackground()
|
||||
except Exception, e:
|
||||
self.logMsg("loadLastBackground Exception : " + str(e), level=0)
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
filterOnParent_Last = WINDOW.getProperty("MB3.Background.Collection")
|
||||
|
||||
last_id = ""
|
||||
self.updateArtLinks()
|
||||
#self.setBackgroundLink(filterOnParent_Last)
|
||||
lastRun = datetime.today()
|
||||
itemLastRun = datetime.today()
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
|
||||
backgroundRefresh = int(addonSettings.getSetting('backgroundRefresh'))
|
||||
if(backgroundRefresh < 10):
|
||||
backgroundRefresh = 10
|
||||
|
||||
itemBackgroundRefresh = 5
|
||||
lastUserName = addonSettings.getSetting('username')
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
td = datetime.today() - lastRun
|
||||
td2 = datetime.today() - itemLastRun
|
||||
secTotal = td.seconds
|
||||
secTotal2 = td2.seconds
|
||||
|
||||
userName = addonSettings.getSetting('username')
|
||||
self.logMsg("Server details string : (" + userName + ") (" + lastUserName + ")", level=2)
|
||||
|
||||
Collection = WINDOW.getProperty("MB3.Background.Collection")
|
||||
if(secTotal > backgroundRefresh or filterOnParent_Last != Collection or userName != lastUserName):
|
||||
lastUserName = userName
|
||||
if(self.linksLoaded == False):
|
||||
self.updateArtLinks()
|
||||
lastRun = datetime.today()
|
||||
filterOnParent_Last = Collection
|
||||
backgroundRefresh = int(addonSettings.getSetting('backgroundRefresh'))
|
||||
self.setBackgroundLink(Collection)
|
||||
if(backgroundRefresh < 10):
|
||||
backgroundRefresh = 10
|
||||
|
||||
# update item BG every 7 seconds
|
||||
if(secTotal2 > itemBackgroundRefresh):
|
||||
self.setItemBackgroundLink()
|
||||
itemLastRun = datetime.today()
|
||||
|
||||
# update item BG on selected item changes
|
||||
if xbmc.getInfoLabel('ListItem.Property(id)') != None:
|
||||
current_id = xbmc.getInfoLabel('ListItem.Property(id)')
|
||||
elif xbmc.getInfoLabel('ListItem.Property(ItemGUID)') != None:
|
||||
current_id=xbmc.getInfoLabel('ListItem.Property(ItemGUID)')
|
||||
else:
|
||||
current_id = ''
|
||||
if current_id != last_id:
|
||||
self.setItemBackgroundLink()
|
||||
itemLastRun = datetime.today()
|
||||
last_id = current_id
|
||||
|
||||
xbmc.sleep(1000)
|
||||
|
||||
try:
|
||||
self.saveLastBackground()
|
||||
except Exception, e:
|
||||
self.logMsg("saveLastBackground Exception : " + str(e), level=0)
|
||||
|
||||
self.logMsg("Exited")
|
||||
|
||||
def loadLastBackground(self):
|
||||
|
||||
__addon__ = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
__addondir__ = xbmc.translatePath( __addon__.getAddonInfo('profile') )
|
||||
|
||||
lastDataPath = __addondir__ + "LastBgLinks.json"
|
||||
dataFile = open(lastDataPath, 'r')
|
||||
jsonData = dataFile.read()
|
||||
dataFile.close()
|
||||
|
||||
self.logMsg(jsonData)
|
||||
result = json.loads(jsonData)
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
if(result.get("global") != None):
|
||||
WINDOW.setProperty("MB3.Background.Global.FanArt", result.get("global")["url"])
|
||||
self.logMsg("MB3.Background.Global.FanArt=" + result.get("global")["url"], level=2)
|
||||
WINDOW.setProperty("MB3.Background.Global.FanArt.Poster", result.get("global")["poster"])
|
||||
self.logMsg("MB3.Background.Global.FanArt.Poster=" + result.get("global")["poster"], level=2)
|
||||
WINDOW.setProperty("MB3.Background.Global.FanArt.Action", result.get("global")["action"])
|
||||
self.logMsg("MB3.Background.Global.FanArt.Action=" + result.get("global")["action"], level=2)
|
||||
|
||||
if(result.get("movie") != None):
|
||||
self.logMsg("Setting Movie Last : " + str(result.get("movie")), level=2)
|
||||
WINDOW.setProperty("MB3.Background.Movie.FanArt", result.get("movie")["url"])
|
||||
|
||||
if(result.get("tv") != None):
|
||||
self.logMsg("Setting TV Last : " + str(result.get("tv")), level=2)
|
||||
WINDOW.setProperty("MB3.Background.TV.FanArt", result.get("tv")["url"])
|
||||
|
||||
if(result.get("music") != None):
|
||||
self.logMsg("Setting Music Last : " + str(result.get("music")), level=2)
|
||||
WINDOW.setProperty("MB3.Background.Music.FanArt", result.get("music")["url"])
|
||||
|
||||
def saveLastBackground(self):
|
||||
|
||||
data = {}
|
||||
if(len(self.global_art_links) > 0):
|
||||
data["global"] = self.global_art_links[self.current_global_art]
|
||||
if(len(self.movie_art_links) > 0):
|
||||
data["movie"] = self.movie_art_links[self.current_movie_art]
|
||||
if(len(self.tv_art_links) > 0):
|
||||
data["tv"] = self.tv_art_links[self.current_tv_art]
|
||||
if(len(self.music_art_links) > 0):
|
||||
data["music"] = self.music_art_links[self.current_music_art]
|
||||
|
||||
__addon__ = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
__addondir__ = xbmc.translatePath( __addon__.getAddonInfo('profile') )
|
||||
|
||||
lastDataPath = __addondir__ + "LastBgLinks.json"
|
||||
dataFile = open(lastDataPath, 'w')
|
||||
stringdata = json.dumps(data)
|
||||
self.logMsg("Last Background Links : " + stringdata)
|
||||
dataFile.write(stringdata)
|
||||
dataFile.close()
|
||||
|
||||
def setBackgroundLink(self, filterOnParent):
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
if(len(self.movie_art_links) > 0):
|
||||
self.logMsg("setBackgroundLink index movie_art_links " + str(self.current_movie_art + 1) + " of " + str(len(self.movie_art_links)), level=2)
|
||||
artUrl = self.movie_art_links[self.current_movie_art]["url"]
|
||||
WINDOW.setProperty("MB3.Background.Movie.FanArt", artUrl)
|
||||
self.logMsg("MB3.Background.Movie.FanArt=" + artUrl)
|
||||
self.current_movie_art = self.current_movie_art + 1
|
||||
if(self.current_movie_art == len(self.movie_art_links)):
|
||||
self.current_movie_art = 0
|
||||
|
||||
if(len(self.tv_art_links) > 0):
|
||||
self.logMsg("setBackgroundLink index tv_art_links " + str(self.current_tv_art + 1) + " of " + str(len(self.tv_art_links)), level=2)
|
||||
artUrl = self.tv_art_links[self.current_tv_art]["url"]
|
||||
WINDOW.setProperty("MB3.Background.TV.FanArt", artUrl)
|
||||
self.logMsg("MB3.Background.TV.FanArt=" + artUrl)
|
||||
self.current_tv_art = self.current_tv_art + 1
|
||||
if(self.current_tv_art == len(self.tv_art_links)):
|
||||
self.current_tv_art = 0
|
||||
|
||||
if(len(self.music_art_links) > 0):
|
||||
self.logMsg("setBackgroundLink index music_art_links " + str(self.current_music_art + 1) + " of " + str(len(self.music_art_links)), level=2)
|
||||
artUrl = self.music_art_links[self.current_music_art]["url"]
|
||||
WINDOW.setProperty("MB3.Background.Music.FanArt", artUrl)
|
||||
self.logMsg("MB3.Background.Music.FanArt=" + artUrl)
|
||||
self.current_music_art = self.current_music_art + 1
|
||||
if(self.current_music_art == len(self.music_art_links)):
|
||||
self.current_music_art = 0
|
||||
|
||||
if(len(self.global_art_links) > 0):
|
||||
self.logMsg("setBackgroundLink index global_art_links " + str(self.current_global_art + 1) + " of " + str(len(self.global_art_links)), level=2)
|
||||
|
||||
next, nextItem = self.findNextLink(self.global_art_links, self.current_global_art, filterOnParent)
|
||||
#nextItem = self.global_art_links[self.current_global_art]
|
||||
self.current_global_art = next
|
||||
|
||||
backGroundUrl = nextItem["url"]
|
||||
posterUrl = nextItem["poster"]
|
||||
actionUrl = nextItem["action"]
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
selectAction = addonSettings.getSetting('selectAction')
|
||||
if(selectAction == "1"):
|
||||
actionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?id=" + nextItem["id"] + "&mode=17)"
|
||||
else:
|
||||
actionUrl = nextItem["action"]
|
||||
|
||||
WINDOW.setProperty("MB3.Background.Global.FanArt", backGroundUrl)
|
||||
self.logMsg("MB3.Background.Global.FanArt=" + backGroundUrl)
|
||||
WINDOW.setProperty("MB3.Background.Global.FanArt.Poster", posterUrl)
|
||||
self.logMsg("MB3.Background.Global.FanArt.Poster=" + posterUrl)
|
||||
WINDOW.setProperty("MB3.Background.Global.FanArt.Action", actionUrl)
|
||||
self.logMsg("MB3.Background.Global.FanArt.Action=" + actionUrl)
|
||||
|
||||
|
||||
def findNextLink(self, linkList, startIndex, filterOnParent):
|
||||
|
||||
if(filterOnParent == None or filterOnParent == ""):
|
||||
filterOnParent = "empty"
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
backgroundRefresh = int(addonSettings.getSetting('backgroundRefresh'))
|
||||
if(backgroundRefresh < 10):
|
||||
backgroundRefresh = 10
|
||||
|
||||
# first check the cache if we are filtering
|
||||
if(self.currentFilteredIndex.get(filterOnParent) != None):
|
||||
cachedItem = self.currentFilteredIndex.get(filterOnParent)
|
||||
self.logMsg("filterOnParent=existing=" + filterOnParent + "=" + str(cachedItem))
|
||||
cachedIndex = cachedItem[0]
|
||||
dateStamp = cachedItem[1]
|
||||
td = datetime.today() - dateStamp
|
||||
secTotal = td.seconds
|
||||
if(secTotal < backgroundRefresh):
|
||||
# use the cached background index
|
||||
self.logMsg("filterOnParent=using=" + filterOnParent + "=" + str(secTotal))
|
||||
return (cachedIndex, linkList[cachedIndex])
|
||||
|
||||
currentIndex = startIndex
|
||||
|
||||
isParentMatch = False
|
||||
|
||||
#xbmc.log("findNextLink : filterOnParent=" + str(filterOnParent) + " isParentMatch=" + str(isParentMatch))
|
||||
|
||||
while(isParentMatch == False):
|
||||
|
||||
currentIndex = currentIndex + 1
|
||||
|
||||
if(currentIndex == len(linkList)):
|
||||
currentIndex = 0
|
||||
|
||||
if(currentIndex == startIndex):
|
||||
return (currentIndex, linkList[currentIndex]) # we checked everything and nothing was ok so return the first one again
|
||||
|
||||
isParentMatch = True
|
||||
# if filter on not empty then make sure we have a bg from the correct collection
|
||||
if(filterOnParent != "empty"):
|
||||
isParentMatch = filterOnParent in linkList[currentIndex]["collections"]
|
||||
|
||||
# save the cached index
|
||||
cachedItem = [currentIndex, datetime.today()]
|
||||
self.logMsg("filterOnParent=adding=" + filterOnParent + "=" + str(cachedItem))
|
||||
self.currentFilteredIndex[filterOnParent] = cachedItem
|
||||
|
||||
nextIndex = currentIndex + 1
|
||||
|
||||
if(nextIndex == len(linkList)):
|
||||
nextIndex = 0
|
||||
|
||||
return (nextIndex, linkList[currentIndex])
|
||||
|
||||
def updateArtLinks(self):
|
||||
t1 = time.time()
|
||||
result01 = self.updateCollectionArtLinks()
|
||||
t2 = time.time()
|
||||
result02 = self.updateTypeArtLinks()
|
||||
t3 = time.time()
|
||||
diff = t2 - t1
|
||||
xbmc.log("TIMEDIFF01 : " + str(diff))
|
||||
diff = t3 - t2
|
||||
xbmc.log("TIMEDIFF02 : " + str(diff))
|
||||
|
||||
if(result01 and result02):
|
||||
xbmc.log("BackgroundRotationThread Update Links Worked")
|
||||
self.linksLoaded = True
|
||||
else:
|
||||
xbmc.log("BackgroundRotationThread Update Links Failed")
|
||||
self.linksLoaded = False
|
||||
|
||||
|
||||
def updateActionUrls(self):
|
||||
xbmc.log("BackgroundRotationThread updateActionUrls Called")
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
for x in range(0, 10):
|
||||
contentUrl = WINDOW.getProperty("xbmb3c_collection_menuitem_content_" + str(x))
|
||||
if(contentUrl != None):
|
||||
index = contentUrl.find("SessionId=(")
|
||||
if(index > -1):
|
||||
index = index + 11
|
||||
index2 = contentUrl.find(")", index+1)
|
||||
timeNow = time.time()
|
||||
newContentUrl = contentUrl[:index] + str(timeNow) + contentUrl[index2:]
|
||||
xbmc.log("xbmb3c_collection_menuitem_content_" + str(x) + "=" + newContentUrl)
|
||||
WINDOW.setProperty("xbmb3c_collection_menuitem_content_" + str(x), newContentUrl)
|
||||
|
||||
def updateCollectionArtLinks(self):
|
||||
self.logMsg("updateCollectionArtLinks Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
# get the user ID
|
||||
userid = downloadUtils.getUserId()
|
||||
self.logMsg("updateCollectionArtLinks UserID : " + userid)
|
||||
|
||||
userUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/Root?format=json"
|
||||
jsonData = downloadUtils.downloadUrl(userUrl, suppress=False, popup=1 )
|
||||
self.logMsg("updateCollectionArtLinks UserData : " + str(jsonData), 2)
|
||||
result = json.loads(jsonData)
|
||||
|
||||
parentid = result.get("Id")
|
||||
self.logMsg("updateCollectionArtLinks ParentID : " + str(parentid), 2)
|
||||
|
||||
userRootPath = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/items?ParentId=" + parentid + "&SortBy=SortName&Fields=CollectionType,Overview,RecursiveItemCount&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(userRootPath, suppress=False, popup=1 )
|
||||
self.logMsg("updateCollectionArtLinks userRootPath : " + str(jsonData), 2)
|
||||
result = json.loads(jsonData)
|
||||
result = result.get("Items")
|
||||
|
||||
artLinks = {}
|
||||
collection_count = 0
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
# process collections
|
||||
for item in result:
|
||||
|
||||
collectionType = item.get("CollectionType", "")
|
||||
name = item.get("Name")
|
||||
childCount = item.get("RecursiveItemCount")
|
||||
self.logMsg("updateCollectionArtLinks Name : " + name, level=1)
|
||||
self.logMsg("updateCollectionArtLinks RecursiveItemCount : " + str(childCount), level=1)
|
||||
if(childCount == None or childCount == 0):
|
||||
continue
|
||||
|
||||
self.logMsg("updateCollectionArtLinks Processing Collection : " + name + " of type : " + collectionType, level=2)
|
||||
|
||||
#####################################################################################################
|
||||
# Process collection item menu item
|
||||
timeNow = time.time()
|
||||
contentUrl = "plugin://plugin.video.xbmb3c?mode=16&ParentId=" + item.get("Id") + "&CollectionType=" + collectionType + "&SessionId=(" + str(timeNow) + ")"
|
||||
actionUrl = ("ActivateWindow(VideoLibrary, plugin://plugin.video.xbmb3c/?mode=21&ParentId=" + item.get("Id") + "&Name=" + name + ",return)").encode('utf-8')
|
||||
xbmc.log("COLLECTION actionUrl: " + actionUrl)
|
||||
WINDOW.setProperty("xbmb3c_collection_menuitem_name_" + str(collection_count), name)
|
||||
WINDOW.setProperty("xbmb3c_collection_menuitem_action_" + str(collection_count), actionUrl)
|
||||
WINDOW.setProperty("xbmb3c_collection_menuitem_collection_" + str(collection_count), name)
|
||||
WINDOW.setProperty("xbmb3c_collection_menuitem_content_" + str(collection_count), contentUrl)
|
||||
#####################################################################################################
|
||||
|
||||
#####################################################################################################
|
||||
# Process collection item backgrounds
|
||||
collectionUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/items?ParentId=" + item.get("Id") + "&IncludeItemTypes=Movie,Series,Episode,MusicArtist,Trailer,MusicVideo,Video&Fields=ParentId,Overview&Recursive=true&CollapseBoxSetItems=false&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(collectionUrl, suppress=False, popup=1 )
|
||||
collectionResult = json.loads(jsonData)
|
||||
|
||||
collectionResult = collectionResult.get("Items")
|
||||
if(collectionResult == None):
|
||||
collectionResult = []
|
||||
|
||||
for col_item in collectionResult:
|
||||
|
||||
id = col_item.get("Id")
|
||||
name = col_item.get("Name")
|
||||
images = col_item.get("BackdropImageTags")
|
||||
|
||||
if(images != None and len(images) > 0):
|
||||
stored_item = artLinks.get(id)
|
||||
|
||||
if(stored_item == None):
|
||||
|
||||
stored_item = {}
|
||||
collections = []
|
||||
collections.append(item.get("Name"))
|
||||
stored_item["collections"] = collections
|
||||
links = []
|
||||
images = col_item.get("BackdropImageTags")
|
||||
parentID = col_item.get("ParentId")
|
||||
name = col_item.get("Name")
|
||||
if (images == None):
|
||||
images = []
|
||||
index = 0
|
||||
|
||||
# build poster image link
|
||||
posterImage = ""
|
||||
actionUrl = ""
|
||||
if(col_item.get("Type") == "Movie" or col_item.get("Type") == "Trailer" or col_item.get("Type") == "MusicVideo" or col_item.get("Type") == "Video"):
|
||||
posterImage = downloadUtils.getArtwork(col_item, "Primary")
|
||||
url = mb3Host + ":" + mb3Port + ',;' + id
|
||||
url = urllib.quote(url)
|
||||
#actionUrl = "ActivateWindow(VideoLibrary, plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + url + " ,return)"
|
||||
actionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + url + ")"
|
||||
|
||||
elif(col_item.get("Type") == "Series"):
|
||||
posterImage = downloadUtils.getArtwork(col_item, "Primary")
|
||||
actionUrl = "ActivateWindow(VideoLibrary, plugin://plugin.video.xbmb3c/?mode=21&ParentId=" + id + "&Name=" + name + ",return)"
|
||||
plot = col_item.get("Overview")
|
||||
for backdrop in images:
|
||||
|
||||
info = {}
|
||||
info["url"] = downloadUtils.getArtwork(col_item, "Backdrop", index=str(index))
|
||||
info["poster"] = posterImage
|
||||
info["action"] = actionUrl
|
||||
info["index"] = index
|
||||
info["id"] = id
|
||||
info["action"] = "None"
|
||||
info["plot"] = plot
|
||||
info["parent"] = parentID
|
||||
info["name"] = name
|
||||
links.append(info)
|
||||
index = index + 1
|
||||
|
||||
stored_item["links"] = links
|
||||
artLinks[id] = stored_item
|
||||
else:
|
||||
stored_item["collections"].append(item.get("Name"))
|
||||
#####################################################################################################
|
||||
|
||||
collection_count = collection_count + 1
|
||||
|
||||
# build global link list
|
||||
final_global_art = []
|
||||
|
||||
for id in artLinks:
|
||||
item = artLinks.get(id)
|
||||
collections = item.get("collections")
|
||||
links = item.get("links")
|
||||
|
||||
for link_item in links:
|
||||
link_item["collections"] = collections
|
||||
final_global_art.append(link_item)
|
||||
#xbmc.log("COLLECTION_DATA GROUPS " + str(link_item))
|
||||
|
||||
self.global_art_links = final_global_art
|
||||
random.shuffle(self.global_art_links)
|
||||
self.logMsg("Background Global Art Links : " + str(len(self.global_art_links)))
|
||||
|
||||
return True
|
||||
|
||||
def updateTypeArtLinks(self):
|
||||
self.logMsg("updateTypeArtLinks Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
# get the user ID
|
||||
userid = downloadUtils.getUserId()
|
||||
self.logMsg("updateTypeArtLinks UserID : " + userid)
|
||||
|
||||
# load Movie BG
|
||||
moviesUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Fields=ParentId,Overview&CollapseBoxSetItems=false&Recursive=true&IncludeItemTypes=Movie&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(moviesUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
for item in result:
|
||||
images = item.get("BackdropImageTags")
|
||||
id = item.get("Id")
|
||||
parentID = item.get("ParentId")
|
||||
name = item.get("Name")
|
||||
plot = item.get("Overview")
|
||||
url = mb3Host + ":" + mb3Port + ',;' + id
|
||||
url = urllib.quote(url)
|
||||
actionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + url + ")"
|
||||
if (images == None):
|
||||
images = []
|
||||
index = 0
|
||||
|
||||
trailerActionUrl = None
|
||||
if item.get("LocalTrailerCount") != None and item.get("LocalTrailerCount") > 0:
|
||||
itemTrailerUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/" + id + "/LocalTrailers?format=json"
|
||||
jsonData = downloadUtils.downloadUrl(itemTrailerUrl, suppress=False, popup=1 )
|
||||
trailerItem = json.loads(jsonData)
|
||||
trailerUrl = mb3Host + ":" + mb3Port + ',;' + trailerItem[0].get("Id")
|
||||
trailerUrl = urllib.quote(trailerUrl)
|
||||
trailerActionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + trailerUrl + ")"
|
||||
|
||||
for backdrop in images:
|
||||
|
||||
info = {}
|
||||
info["url"] = downloadUtils.getArtwork(item, "Backdrop", index=str(index))
|
||||
info["index"] = index
|
||||
info["id"] = id
|
||||
info["plot"] = plot
|
||||
info["action"] = actionUrl
|
||||
info["trailer"] = trailerActionUrl
|
||||
info["parent"] = parentID
|
||||
info["name"] = name
|
||||
self.logMsg("BG Movie Image Info : " + str(info), level=2)
|
||||
|
||||
if (info not in self.movie_art_links):
|
||||
self.movie_art_links.append(info)
|
||||
index = index + 1
|
||||
|
||||
random.shuffle(self.movie_art_links)
|
||||
self.logMsg("Background Movie Art Links : " + str(len(self.movie_art_links)))
|
||||
|
||||
# load TV BG links
|
||||
tvUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Fields=ParentId,Overview&CollapseBoxSetItems=false&Recursive=true&IncludeItemTypes=Series&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(tvUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
for item in result:
|
||||
images = item.get("BackdropImageTags")
|
||||
id = item.get("Id")
|
||||
parentID = item.get("ParentId")
|
||||
name = item.get("Name")
|
||||
plot = item.get("Overview")
|
||||
if (images == None):
|
||||
images = []
|
||||
index = 0
|
||||
for backdrop in images:
|
||||
|
||||
info = {}
|
||||
info["url"] = downloadUtils.getArtwork(item, "Backdrop", index=str(index))
|
||||
info["index"] = index
|
||||
info["id"] = id
|
||||
info["action"] = "None"
|
||||
info["trailer"] = "None"
|
||||
info["plot"] = plot
|
||||
info["parent"] = parentID
|
||||
info["name"] = name
|
||||
self.logMsg("BG TV Image Info : " + str(info), level=2)
|
||||
|
||||
if (info not in self.tv_art_links):
|
||||
self.tv_art_links.append(info)
|
||||
index = index + 1
|
||||
|
||||
random.shuffle(self.tv_art_links)
|
||||
self.logMsg("Background Tv Art Links : " + str(len(self.tv_art_links)))
|
||||
|
||||
# load music BG links
|
||||
musicUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Fields=ParentId,Overview&CollapseBoxSetItems=false&Recursive=true&IncludeItemTypes=MusicArtist&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(musicUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
for item in result:
|
||||
images = item.get("BackdropImageTags")
|
||||
id = item.get("Id")
|
||||
parentID = item.get("ParentId")
|
||||
name = item.get("Name")
|
||||
plot = item.get("Overview")
|
||||
if (images == None):
|
||||
images = []
|
||||
index = 0
|
||||
for backdrop in images:
|
||||
|
||||
info = {}
|
||||
info["url"] = downloadUtils.getArtwork(item, "Backdrop", index=str(index))
|
||||
info["index"] = index
|
||||
info["id"] = id
|
||||
info["action"] = "None"
|
||||
info["trailer"] = "None"
|
||||
info["plot"] = plot
|
||||
info["parent"] = parentID
|
||||
info["name"] = name
|
||||
self.logMsg("BG Music Image Info : " + str(info), level=2)
|
||||
|
||||
if (info not in self.music_art_links):
|
||||
self.music_art_links.append(info)
|
||||
index = index + 1
|
||||
|
||||
random.shuffle(self.music_art_links)
|
||||
self.logMsg("Background Music Art Links : " + str(len(self.music_art_links)))
|
||||
|
||||
#
|
||||
# build a map indexed by id that contains a list of BG art for each item
|
||||
# this is used for selected item BG rotation
|
||||
#
|
||||
self.item_art_links = {}
|
||||
|
||||
# add movie BG links
|
||||
for bg_item in self.movie_art_links:
|
||||
item_id = bg_item["id"]
|
||||
if(self.item_art_links.get(item_id) != None):
|
||||
self.item_art_links[item_id].append(bg_item)
|
||||
else:
|
||||
bg_list = []
|
||||
bg_list.append(bg_item)
|
||||
self.item_art_links[item_id] = bg_list
|
||||
|
||||
# add TV BG links
|
||||
for bg_item in self.tv_art_links:
|
||||
item_id = bg_item["id"]
|
||||
if(self.item_art_links.get(item_id) != None):
|
||||
self.item_art_links[item_id].append(bg_item)
|
||||
else:
|
||||
bg_list = []
|
||||
bg_list.append(bg_item)
|
||||
self.item_art_links[item_id] = bg_list
|
||||
|
||||
# add music BG links
|
||||
for bg_item in self.music_art_links:
|
||||
item_id = bg_item["id"]
|
||||
if(self.item_art_links.get(item_id) != None):
|
||||
self.item_art_links[item_id].append(bg_item)
|
||||
else:
|
||||
bg_list = []
|
||||
bg_list.append(bg_item)
|
||||
self.item_art_links[item_id] = bg_list
|
||||
|
||||
|
||||
return True
|
||||
|
||||
def setItemBackgroundLink(self):
|
||||
|
||||
id = xbmc.getInfoLabel('ListItem.Property(ItemGUID)')
|
||||
self.logMsg("setItemBackgroundLink ItemGUID : " + id, 1)
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
if id != None and id != "":
|
||||
|
||||
listOfBackgrounds = self.item_art_links.get(id)
|
||||
listOfData = self.item_art_links.get(xbmc.getInfoLabel('ListItem.Property(id)'))
|
||||
|
||||
# if for some reson the item is not in the cache try to load it now
|
||||
if(listOfBackgrounds == None or len(listOfBackgrounds) == 0):
|
||||
self.loadItemBackgroundLinks(id)
|
||||
if(listOfData == None or len(listOfData) == 0):
|
||||
self.loadItemBackgroundLinks(xbmc.getInfoLabel('ListItem.Property(id)'))
|
||||
|
||||
|
||||
listOfBackgrounds = self.item_art_links.get(id)
|
||||
listOfData = self.item_art_links.get(xbmc.getInfoLabel('ListItem.Property(id)'))
|
||||
|
||||
if listOfBackgrounds != None:
|
||||
if listOfData != None:
|
||||
if listOfData[0]["plot"] != "" and listOfData[0]["plot"] != None:
|
||||
plot=listOfData[0]["plot"]
|
||||
plot=plot.encode("utf-8")
|
||||
WINDOW.setProperty("MB3.Plot", plot )
|
||||
else:
|
||||
WINDOW.clearProperty("MB3.Plot")
|
||||
|
||||
if listOfBackgrounds[0]["action"] != None and listOfBackgrounds[0]["action"] != "":
|
||||
action=listOfBackgrounds[0]["action"]
|
||||
WINDOW.setProperty("MB3.Action", action )
|
||||
else:
|
||||
WINDOW.clearProperty("MB3.Action")
|
||||
|
||||
if listOfBackgrounds[0].get("trailer") != None and listOfBackgrounds[0]["trailer"] != "":
|
||||
trailerAction=listOfBackgrounds[0]["trailer"]
|
||||
WINDOW.setProperty("MB3.TrailerAction", trailerAction )
|
||||
else:
|
||||
WINDOW.clearProperty("MB3.TrailerAction")
|
||||
|
||||
if(listOfBackgrounds != None and len(listOfBackgrounds) > 0):
|
||||
self.logMsg("setItemBackgroundLink Image " + str(self.current_item_art + 1) + " of " + str(len(listOfBackgrounds)), 1)
|
||||
try:
|
||||
artUrl = listOfBackgrounds[self.current_item_art]["url"]
|
||||
except IndexError:
|
||||
self.current_item_art = 0
|
||||
artUrl = listOfBackgrounds[self.current_item_art]["url"]
|
||||
|
||||
WINDOW.setProperty("MB3.Background.Item.FanArt", artUrl)
|
||||
self.logMsg("setItemBackgroundLink MB3.Background.Item.FanArt=" + artUrl, 1)
|
||||
|
||||
self.current_item_art = self.current_item_art + 1
|
||||
if(self.current_item_art == len(listOfBackgrounds) - 1):
|
||||
self.current_item_art = 0
|
||||
|
||||
else:
|
||||
self.logMsg("setItemBackgroundLink Resetting MB3.Background.Item.FanArt", 1)
|
||||
WINDOW.clearProperty("MB3.Background.Item.FanArt")
|
||||
|
||||
else:
|
||||
self.logMsg("setItemBackgroundLink Resetting MB3.Background.Item.FanArt", 1)
|
||||
WINDOW.clearProperty("MB3.Background.Item.FanArt")
|
||||
WINDOW.clearProperty("MB3.Plot")
|
||||
WINDOW.clearProperty("MB3.Action")
|
||||
WINDOW.clearProperty("MB3.TrailerAction")
|
||||
|
||||
|
||||
def loadItemBackgroundLinks(self, id):
|
||||
|
||||
if(id == None or len(id) == 0):
|
||||
self.logMsg("loadItemBackgroundLinks id was empty")
|
||||
return
|
||||
|
||||
self.logMsg("loadItemBackgroundLinks Called for id : " + id)
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
itemUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/" + id + "?Fields=ParentId,Overview&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(itemUrl, suppress=False, popup=1 )
|
||||
item = json.loads(jsonData)
|
||||
|
||||
self.logMsg("loadItemBackgroundLinks found item : " + str(item), 2);
|
||||
|
||||
if(item == None):
|
||||
item = []
|
||||
|
||||
#for item in result:
|
||||
images = item.get("BackdropImageTags")
|
||||
plot = item.get("Overview")
|
||||
id = item.get("Id")
|
||||
urlid = id
|
||||
parentID = item.get("ParentId")
|
||||
origid = id
|
||||
name = item.get("Name")
|
||||
if (images == None or images == []):
|
||||
images = item.get("ParentBackdropImageTags")
|
||||
urlid = item.get("ParentBackdropItemId")
|
||||
if (images == None):
|
||||
images = []
|
||||
|
||||
index = 0
|
||||
url = mb3Host + ":" + mb3Port + ',;' + id
|
||||
url = urllib.quote(url)
|
||||
actionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + url + ")"
|
||||
trailerActionUrl = None
|
||||
if item.get("LocalTrailerCount") != None and item.get("LocalTrailerCount") > 0:
|
||||
itemTrailerUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/" + id + "/LocalTrailers?format=json"
|
||||
jsonData = downloadUtils.downloadUrl(itemTrailerUrl, suppress=False, popup=1 )
|
||||
trailerItem = json.loads(jsonData)
|
||||
trailerUrl = mb3Host + ":" + mb3Port + ',;' + trailerItem[0].get("Id")
|
||||
trailerUrl = urllib.quote(trailerUrl)
|
||||
trailerActionUrl = "RunPlugin(plugin://plugin.video.xbmb3c/?mode=" + str(_MODE_BASICPLAY) + "&url=" + trailerUrl + ")"
|
||||
|
||||
newBgLinks = []
|
||||
for backdrop in images:
|
||||
info = {}
|
||||
info["url"] = downloadUtils.getArtwork(item, "Backdrop", index=str(index))
|
||||
info["plot"] = plot
|
||||
info["action"] = actionUrl
|
||||
info["trailer"] = trailerActionUrl
|
||||
info["index"] = index
|
||||
info["id"] = urlid
|
||||
info["parent"] = parentID
|
||||
info["name"] = name
|
||||
self.logMsg("BG Item Image Info : " + str(info), level=2)
|
||||
newBgLinks.append(info)
|
||||
index = index + 1
|
||||
|
||||
if(len(newBgLinks) > 0):
|
||||
self.item_art_links[origid] = newBgLinks
|
||||
|
||||
|
||||
|
11
resources/lib/ClientInformation.py
Normal file
11
resources/lib/ClientInformation.py
Normal file
@ -0,0 +1,11 @@
|
||||
from uuid import getnode as get_mac
|
||||
import xbmcaddon
|
||||
|
||||
class ClientInformation():
|
||||
|
||||
def getMachineId(self):
|
||||
return "%012X"%get_mac()
|
||||
|
||||
def getVersion(self):
|
||||
version = xbmcaddon.Addon(id="plugin.video.xbmb3c").getAddonInfo("version")
|
||||
return version
|
422
resources/lib/DownloadUtils.py
Normal file
422
resources/lib/DownloadUtils.py
Normal file
@ -0,0 +1,422 @@
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import urllib
|
||||
import urllib2
|
||||
import httplib
|
||||
import requests
|
||||
import hashlib
|
||||
import StringIO
|
||||
import gzip
|
||||
import sys
|
||||
import json as json
|
||||
from random import randrange
|
||||
from uuid import getnode as get_mac
|
||||
from ClientInformation import ClientInformation
|
||||
|
||||
class DownloadUtils():
|
||||
|
||||
logLevel = 0
|
||||
addonSettings = None
|
||||
getString = None
|
||||
|
||||
def __init__(self, *args):
|
||||
self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
self.getString = self.addonSettings.getLocalizedString
|
||||
level = self.addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C DownloadUtils -> " + msg)
|
||||
|
||||
def getUserId(self):
|
||||
|
||||
port = self.addonSettings.getSetting('port')
|
||||
host = self.addonSettings.getSetting('ipaddress')
|
||||
userName = self.addonSettings.getSetting('username')
|
||||
|
||||
self.logMsg("Looking for user name: " + userName)
|
||||
|
||||
jsonData = None
|
||||
try:
|
||||
jsonData = self.downloadUrl(host + ":" + port + "/mediabrowser/Users/Public?format=json")
|
||||
except Exception, msg:
|
||||
error = "Get User unable to connect to " + host + ":" + port + " : " + str(msg)
|
||||
xbmc.log (error)
|
||||
return ""
|
||||
|
||||
|
||||
self.logMsg("GETUSER_JSONDATA_01:" + str(jsonData))
|
||||
|
||||
result = []
|
||||
|
||||
try:
|
||||
result = json.loads(jsonData)
|
||||
except Exception, e:
|
||||
self.logMsg("jsonload : " + str(e) + " (" + jsonData + ")", level=1)
|
||||
return ""
|
||||
|
||||
self.logMsg("GETUSER_JSONDATA_02:" + str(result))
|
||||
|
||||
userid = ""
|
||||
secure = False
|
||||
for user in result:
|
||||
if(user.get("Name") == userName):
|
||||
userid = user.get("Id")
|
||||
self.logMsg("Username Found:" + user.get("Name"))
|
||||
if(user.get("HasPassword") == True):
|
||||
secure = True
|
||||
self.logMsg("Username Is Secure (HasPassword=True)")
|
||||
break
|
||||
|
||||
if(secure):
|
||||
self.authenticate('http://' + host + ":" + port + "/mediabrowser/Users/AuthenticateByName?format=json")
|
||||
|
||||
if userid == "":
|
||||
return_value = xbmcgui.Dialog().ok(self.getString(30045),self.getString(30045))
|
||||
sys.exit()
|
||||
|
||||
self.logMsg("userid : " + userid)
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
WINDOW.setProperty("userid", userid)
|
||||
|
||||
return userid
|
||||
|
||||
def getMachineId(self):
|
||||
return "%012X"%get_mac()
|
||||
|
||||
def authenticate(self, url):
|
||||
txt_mac = self.getMachineId()
|
||||
version = ClientInformation().getVersion()
|
||||
|
||||
deviceName = self.addonSettings.getSetting('deviceName')
|
||||
deviceName = deviceName.replace("\"", "_")
|
||||
|
||||
authString = "Mediabrowser Client=\"XBMC\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
|
||||
headers = {'Accept-encoding': 'gzip', 'Authorization' : authString}
|
||||
sha1 = hashlib.sha1(self.addonSettings.getSetting('password'))
|
||||
resp = requests.post(url, data={'password':sha1.hexdigest(),'Username':self.addonSettings.getSetting('username')}, headers=headers)
|
||||
code=str(resp.status_code)
|
||||
result = resp.json()
|
||||
if result.get("AccessToken") != self.addonSettings.getSetting('AccessToken'):
|
||||
self.addonSettings.setSetting('AccessToken', result.get("AccessToken"))
|
||||
if int(code) >= 200 and int(code)<300:
|
||||
self.logMsg("User Authenticated")
|
||||
else:
|
||||
self.logMsg("User NOT Authenticated")
|
||||
return_value = xbmcgui.Dialog().ok(self.getString(30044), self.getString(30044))
|
||||
sys.exit()
|
||||
|
||||
def getArtwork(self, data, type, index = "0", userParentInfo = False):
|
||||
|
||||
id = data.get("Id")
|
||||
getSeriesData = False
|
||||
|
||||
if type == "tvshow.poster": # Change the Id to the series to get the overall series poster
|
||||
if data.get("Type") == "Season" or data.get("Type")== "Episode":
|
||||
id = data.get("SeriesId")
|
||||
getSeriesData = True
|
||||
elif type == "poster" and data.get("Type") == "Episode" and self.addonSettings.getSetting('useSeasonPoster')=='true': # Change the Id to the Season to get the season poster
|
||||
id = data.get("SeasonId")
|
||||
if type == "poster" or type == "tvshow.poster": # Now that the Ids are right, change type to MB3 name
|
||||
type="Primary"
|
||||
if data.get("Type") == "Season": # For seasons: primary (poster), thumb and banner get season art, rest series art
|
||||
if type != "Primary" and type != "Thumb" and type != "Banner":
|
||||
id = data.get("SeriesId")
|
||||
getSeriesData = True
|
||||
if data.get("Type") == "Episode": # For episodes: primary (episode thumb) gets episode art, rest series art.
|
||||
if type != "Primary":
|
||||
id = data.get("SeriesId")
|
||||
getSeriesData = True
|
||||
|
||||
# if requested get parent info
|
||||
if getSeriesData == True and userParentInfo == True:
|
||||
self.logMsg("Using Parent Info for image link", level=1)
|
||||
mb3Host = self.addonSettings.getSetting('ipaddress')
|
||||
mb3Port = self.addonSettings.getSetting('port')
|
||||
userid = self.getUserId()
|
||||
seriesJsonData = self.downloadUrl("http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/" + id + "?format=json", suppress=False, popup=1 )
|
||||
seriesResult = json.loads(seriesJsonData)
|
||||
data = seriesResult
|
||||
|
||||
imageTag = "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format
|
||||
originalType = type
|
||||
if type == "Primary2" or type == "Primary3" or type=="SeriesPrimary":
|
||||
type = "Primary"
|
||||
if type == "Backdrop2" or type=="Backdrop3":
|
||||
type = "Backdrop"
|
||||
if type == "Thumb2" or type=="Thumb3":
|
||||
type = "Thumb"
|
||||
if(data.get("ImageTags") != None and data.get("ImageTags").get(type) != None):
|
||||
imageTag = data.get("ImageTags").get(type)
|
||||
|
||||
query = ""
|
||||
height = "10000"
|
||||
width = "10000"
|
||||
played = "0"
|
||||
|
||||
if self.addonSettings.getSetting('showIndicators')=='true': # add watched, unplayedcount and percentage played indicators to posters
|
||||
|
||||
if (originalType =="Primary" or originalType =="Backdrop") and data.get("Type") != "Episode":
|
||||
userData = data.get("UserData")
|
||||
if originalType =="Backdrop" and index == "0":
|
||||
totalbackdrops = len(data.get("BackdropImageTags"))
|
||||
if totalbackdrops != 0:
|
||||
index = str(randrange(0,totalbackdrops))
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
|
||||
if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'):
|
||||
query = query + "&AddPlayedIndicator=true"
|
||||
|
||||
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
|
||||
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
|
||||
PlayedPercentage = userData.get("PlayedPercentage")
|
||||
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
elif originalType =="Primary2" and data.get("Type") != "Episode":
|
||||
userData = data.get("UserData")
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'):
|
||||
query = query + "&AddPlayedIndicator=true"
|
||||
|
||||
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
|
||||
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
|
||||
PlayedPercentage = userData.get("PlayedPercentage")
|
||||
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "340"
|
||||
width = "226"
|
||||
|
||||
elif (originalType =="Primary3" and data.get("Type") != "Episode") or originalType == "SeriesPrimary":
|
||||
userData = data.get("UserData")
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'):
|
||||
query = query + "&AddPlayedIndicator=true"
|
||||
|
||||
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
|
||||
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
|
||||
PlayedPercentage = userData.get("PlayedPercentage")
|
||||
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "800"
|
||||
width = "550"
|
||||
|
||||
elif type =="Primary" and data.get("Type") == "Episode":
|
||||
userData = data.get("UserData")
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'):
|
||||
query = query + "&AddPlayedIndicator=true"
|
||||
|
||||
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
|
||||
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
|
||||
PlayedPercentage = userData.get("PlayedPercentage")
|
||||
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "410"
|
||||
width = "770"
|
||||
|
||||
elif originalType =="Backdrop2" or originalType =="Thumb2" and data.get("Type") != "Episode":
|
||||
userData = data.get("UserData")
|
||||
if originalType =="Backdrop2":
|
||||
totalbackdrops = len(data.get("BackdropImageTags"))
|
||||
if totalbackdrops != 0:
|
||||
index = str(randrange(0,totalbackdrops))
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'):
|
||||
query = query + "&AddPlayedIndicator=true"
|
||||
|
||||
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
|
||||
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
|
||||
PlayedPercentage = userData.get("PlayedPercentage")
|
||||
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "270"
|
||||
width = "480"
|
||||
|
||||
elif originalType =="Backdrop3" or originalType =="Thumb3" and data.get("Type") != "Episode":
|
||||
userData = data.get("UserData")
|
||||
if originalType =="Backdrop3":
|
||||
totalbackdrops = len(data.get("BackdropImageTags"))
|
||||
if totalbackdrops != 0:
|
||||
index = str(randrange(0,totalbackdrops))
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and self.addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and self.addonSettings.getSetting('showWatchedIndicators')=='true'):
|
||||
query = query + "&AddPlayedIndicator=true"
|
||||
|
||||
PlayedPercentage = 0 if userData.get("PlayedPercentage")==None else userData.get("PlayedPercentage")
|
||||
if PlayedPercentage == 0 and userData!=None and userData.get("PlayedPercentage")!=None :
|
||||
PlayedPercentage = userData.get("PlayedPercentage")
|
||||
if (PlayedPercentage != 100 or PlayedPercentage) != 0 and self.addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "800"
|
||||
width = "1420"
|
||||
|
||||
# use the local image proxy server that is made available by this addons service
|
||||
|
||||
port = self.addonSettings.getSetting('port')
|
||||
host = self.addonSettings.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
artwork = "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + index + "/" + imageTag + "/original/" + height + "/" + width + "/" + played + "?" + query
|
||||
if self.addonSettings.getSetting('disableCoverArt')=='true':
|
||||
artwork = artwork + "&EnableImageEnhancers=false"
|
||||
|
||||
self.logMsg("getArtwork : " + artwork, level=2)
|
||||
|
||||
# do not return non-existing images
|
||||
if ( (type!="Backdrop" and imageTag=="") |
|
||||
(type=="Backdrop" and data.get("BackdropImageTags") != None and len(data.get("BackdropImageTags")) == 0) |
|
||||
(type=="Backdrop" and data.get("BackdropImageTag") != None and len(data.get("BackdropImageTag")) == 0)
|
||||
):
|
||||
if type=="Backdrop" and getSeriesData==True and data.get("ParentBackdropImageTags") == None:
|
||||
artwork=''
|
||||
|
||||
return artwork
|
||||
|
||||
def getUserArtwork(self, data, type, index = "0"):
|
||||
|
||||
id = data.get("Id")
|
||||
#query = "&type=" + type + "&tag=" + imageTag
|
||||
query = ""
|
||||
height = "60"
|
||||
width = "60"
|
||||
played = "0"
|
||||
|
||||
# use the local image proxy server that is made available by this addons service
|
||||
port = self.addonSettings.getSetting('port')
|
||||
host = self.addonSettings.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
artwork = "http://" + server + "/mediabrowser/Users/" + str(id) + "/Images/Primary/0" + "?height=60&width=60&format=png"
|
||||
|
||||
return artwork
|
||||
|
||||
def imageUrl(self, id, type, index, width, height):
|
||||
|
||||
port = self.addonSettings.getSetting('port')
|
||||
host = self.addonSettings.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
return "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + str(index) + "/e3ab56fe27d389446754d0fb04910a34/original/" + str(height) + "/" + str(width) + "/0"
|
||||
|
||||
def downloadUrl(self, url, suppress=False, type="GET", popup=0 ):
|
||||
self.logMsg("== ENTER: getURL ==")
|
||||
try:
|
||||
if url[0:4] == "http":
|
||||
serversplit=2
|
||||
urlsplit=3
|
||||
else:
|
||||
serversplit=0
|
||||
urlsplit=1
|
||||
|
||||
server=url.split('/')[serversplit]
|
||||
urlPath="/"+"/".join(url.split('/')[urlsplit:])
|
||||
|
||||
self.logMsg("url = " + url)
|
||||
self.logMsg("server = "+str(server), level=2)
|
||||
self.logMsg("urlPath = "+str(urlPath), level=2)
|
||||
conn = httplib.HTTPConnection(server, timeout=20)
|
||||
#head = {"Accept-Encoding" : "gzip,deflate", "Accept-Charset" : "UTF-8,*"}
|
||||
if self.addonSettings.getSetting('AccessToken')==None:
|
||||
self.addonSettings.setSetting('AccessToken','')
|
||||
head = {"Accept-Encoding" : "gzip", "Accept-Charset" : "UTF-8,*", "X-MediaBrowser-Token" : self.addonSettings.getSetting('AccessToken')}
|
||||
#head = getAuthHeader()
|
||||
conn.request(method=type, url=urlPath, headers=head)
|
||||
#conn.request(method=type, url=urlPath)
|
||||
data = conn.getresponse()
|
||||
self.logMsg("GET URL HEADERS : " + str(data.getheaders()), level=2)
|
||||
link = ""
|
||||
contentType = "none"
|
||||
if int(data.status) == 200:
|
||||
retData = data.read()
|
||||
contentType = data.getheader('content-encoding')
|
||||
self.logMsg("Data Len Before : " + str(len(retData)))
|
||||
if(contentType == "gzip"):
|
||||
retData = StringIO.StringIO(retData)
|
||||
gzipper = gzip.GzipFile(fileobj=retData)
|
||||
link = gzipper.read()
|
||||
else:
|
||||
link = retData
|
||||
|
||||
self.logMsg("Data Len After : " + str(len(link)))
|
||||
self.logMsg("====== 200 returned =======")
|
||||
self.logMsg("Content-Type : " + str(contentType))
|
||||
self.logMsg(link)
|
||||
self.logMsg("====== 200 finished ======")
|
||||
|
||||
elif ( int(data.status) == 301 ) or ( int(data.status) == 302 ):
|
||||
try: conn.close()
|
||||
except: pass
|
||||
return data.getheader('Location')
|
||||
|
||||
elif int(data.status) >= 400:
|
||||
error = "HTTP response error: " + str(data.status) + " " + str(data.reason)
|
||||
xbmc.log (error)
|
||||
if suppress is False:
|
||||
if popup == 0:
|
||||
xbmc.executebuiltin("XBMC.Notification(URL error: "+ str(data.reason) +",)")
|
||||
else:
|
||||
xbmcgui.Dialog().ok(self.getString(30135),server)
|
||||
xbmc.log (error)
|
||||
try: conn.close()
|
||||
except: pass
|
||||
return ""
|
||||
else:
|
||||
link = ""
|
||||
except Exception, msg:
|
||||
error = "Unable to connect to " + str(server) + " : " + str(msg)
|
||||
xbmc.log (error)
|
||||
xbmc.executebuiltin("XBMC.Notification(\"XBMB3C\": URL error: Unable to connect to server,)")
|
||||
xbmcgui.Dialog().ok("",self.getString(30204))
|
||||
raise
|
||||
else:
|
||||
try: conn.close()
|
||||
except: pass
|
||||
|
||||
return link
|
312
resources/lib/InProgressItems.py
Normal file
312
resources/lib/InProgressItems.py
Normal file
@ -0,0 +1,312 @@
|
||||
#################################################################################################
|
||||
# In Progress Updater
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
class InProgressUpdaterThread(threading.Thread):
|
||||
|
||||
logLevel = 0
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C InProgressUpdaterThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C InProgressUpdaterThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
self.logMsg("Started")
|
||||
|
||||
self.updateInProgress()
|
||||
lastRun = datetime.today()
|
||||
|
||||
updateInterval = 300
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
td = datetime.today() - lastRun
|
||||
secTotal = td.seconds
|
||||
|
||||
if(secTotal > updateInterval):
|
||||
self.updateInProgress()
|
||||
lastRun = datetime.today()
|
||||
|
||||
xbmc.sleep(3000)
|
||||
|
||||
self.logMsg("Exited")
|
||||
|
||||
def updateInProgress(self):
|
||||
self.logMsg("updateInProgress Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
self.logMsg("InProgress UserName : " + userName + " UserID : " + userid)
|
||||
|
||||
self.logMsg("Updating In Progress Movie List")
|
||||
|
||||
recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=DatePlayed&SortOrder=Descending&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&Filters=IsResumable&IncludeItemTypes=Movie&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
rating = item.get("CommunityRating")
|
||||
criticrating = item.get("CriticRating")
|
||||
officialrating = item.get("OfficialRating")
|
||||
criticratingsummary = ""
|
||||
if(item.get("CriticRatingSummary") != None):
|
||||
criticratingsummary = item.get("CriticRatingSummary").encode('utf-8')
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
year = item.get("ProductionYear")
|
||||
if(item.get("RunTimeTicks") != None):
|
||||
runtime = str(int(item.get("RunTimeTicks"))/(10000000*60))
|
||||
else:
|
||||
runtime = "0"
|
||||
|
||||
userData = item.get("UserData")
|
||||
if(userData != None):
|
||||
reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
|
||||
seekTime = reasonableTicks / 10000
|
||||
duration = float(runtime)
|
||||
resume = float(seekTime) / 60.0
|
||||
if (duration == 0):
|
||||
percentage=0
|
||||
else:
|
||||
percentage = (resume / duration) * 100.0
|
||||
perasint = int(percentage)
|
||||
title = str(perasint) + "% " + title
|
||||
|
||||
item_id = item.get("Id")
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary2")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
medium_fanart = downloadUtils.getArtwork(item, "Backdrop3")
|
||||
|
||||
if item.get("ImageTags").get("Thumb") != None:
|
||||
realthumbnail = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
realthumbnail = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Title = " + title, level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Thumb = " + realthumbnail, level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Year = " + str(year), level=2)
|
||||
self.logMsg("InProgressMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2)
|
||||
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Title", title)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Thumb", realthumbnail)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Art(fanart)", fanart)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Art(medium_fanart)", medium_fanart)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Art(clearlogo)", logo)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Art(poster)", thumbnail)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Rating", str(rating))
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Mpaa", str(officialrating))
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".CriticRating", str(criticrating))
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Plot", plot)
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Year", str(year))
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(item_count) + ".Runtime", str(runtime))
|
||||
|
||||
WINDOW.setProperty("InProgressMovieMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
# blank any not available
|
||||
for x in range(item_count, 11):
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Title", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Thumb", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Path", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Art(fanart)", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Art(clearlogo)", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Art(poster)", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Rating", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".CriticRating", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".CriticRatingSummary", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Plot", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Year", "")
|
||||
WINDOW.setProperty("InProgressMovieMB3." + str(x) + ".Runtime", "")
|
||||
|
||||
|
||||
#Updating Recent TV Show List
|
||||
self.logMsg("Updating In Progress Episode List")
|
||||
|
||||
recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=DatePlayed&SortOrder=Descending&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&Filters=IsResumable&IncludeItemTypes=Episode&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
seriesName = "Missing Name"
|
||||
if(item.get("SeriesName") != None):
|
||||
seriesName = item.get("SeriesName").encode('utf-8')
|
||||
|
||||
eppNumber = "X"
|
||||
tempEpisodeNumber = "00"
|
||||
if(item.get("IndexNumber") != None):
|
||||
eppNumber = item.get("IndexNumber")
|
||||
if eppNumber < 10:
|
||||
tempEpisodeNumber = "0" + str(eppNumber)
|
||||
else:
|
||||
tempEpisodeNumber = str(eppNumber)
|
||||
|
||||
seasonNumber = item.get("ParentIndexNumber")
|
||||
if seasonNumber < 10:
|
||||
tempSeasonNumber = "0" + str(seasonNumber)
|
||||
else:
|
||||
tempSeasonNumber = str(seasonNumber)
|
||||
rating = str(item.get("CommunityRating"))
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
|
||||
if(item.get("RunTimeTicks") != None):
|
||||
runtime = str(int(item.get("RunTimeTicks"))/(10000000*60))
|
||||
else:
|
||||
runtime = "0"
|
||||
|
||||
userData = item.get("UserData")
|
||||
if(userData != None):
|
||||
reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
|
||||
seekTime = reasonableTicks / 10000
|
||||
duration = float(runtime)
|
||||
resume = float(seekTime) / 60.0
|
||||
if (duration == 0):
|
||||
percentage=0
|
||||
else:
|
||||
percentage = (resume / duration) * 100.0
|
||||
perasint = int(percentage)
|
||||
title = str(perasint) + "% " + title
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
if item.get("Type") == "Episode" or item.get("Type") == "Season":
|
||||
series_id = item.get("SeriesId")
|
||||
|
||||
poster = downloadUtils.getArtwork(item, "SeriesPrimary")
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
medium_fanart = downloadUtils.getArtwork(item, "Backdrop3")
|
||||
|
||||
banner = downloadUtils.getArtwork(item, "Banner")
|
||||
if item.get("SeriesThumbImageTag") != None:
|
||||
seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
seriesthumbnail = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2)
|
||||
self.logMsg("InProgresstEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".EpisodeTitle", title)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Rating", rating)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.medium_fanart)", medium_fanart)
|
||||
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster)
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(item_count) + ".Plot", plot)
|
||||
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
# blank any not available
|
||||
for x in range(item_count, 11):
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".EpisodeTitle", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".ShowTitle", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".EpisodeNo", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".SeasonNo", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Thumb", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Path", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Rating", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Art(tvshow.fanart)", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Art(tvshow.clearlogo)", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Art(tvshow.banner)", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Art(tvshow.poster)", "")
|
||||
WINDOW.setProperty("InProgresstEpisodeMB3." + str(x) + ".Plot", "")
|
||||
|
||||
|
||||
|
250
resources/lib/InfoUpdater.py
Normal file
250
resources/lib/InfoUpdater.py
Normal file
@ -0,0 +1,250 @@
|
||||
#################################################################################################
|
||||
# Info Updater
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
class InfoUpdaterThread(threading.Thread):
|
||||
|
||||
logLevel = 0
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C InfoUpdaterThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C InfoUpdaterThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
self.logMsg("Started")
|
||||
|
||||
self.updateInfo()
|
||||
lastRun = datetime.today()
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
td = datetime.today() - lastRun
|
||||
secTotal = td.seconds
|
||||
|
||||
if(secTotal > 300):
|
||||
self.updateInfo()
|
||||
lastRun = datetime.today()
|
||||
|
||||
xbmc.sleep(3000)
|
||||
|
||||
self.logMsg("Exited")
|
||||
|
||||
def updateInfo(self):
|
||||
self.logMsg("updateInfo Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
self.logMsg("updateInfo UserID : " + userid)
|
||||
|
||||
self.logMsg("Updating info List")
|
||||
|
||||
infoUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Fields=CollectionType&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(infoUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
result = result.get("Items")
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
movie_count = 0
|
||||
movie_unwatched_count = 0
|
||||
tv_count = 0
|
||||
episode_count = 0
|
||||
episode_unwatched_count = 0
|
||||
tv_unwatched_count = 0
|
||||
music_count = 0
|
||||
music_songs_count = 0
|
||||
music_songs_unplayed_count = 0
|
||||
musicvideos_count = 0
|
||||
musicvideos_unwatched_count = 0
|
||||
trailers_count = 0
|
||||
trailers_unwatched_count = 0
|
||||
photos_count = 0
|
||||
channels_count = 0
|
||||
for item in result:
|
||||
collectionType = item.get("CollectionType")
|
||||
if collectionType==None:
|
||||
collectionType="unknown"
|
||||
self.logMsg("collectionType " + collectionType)
|
||||
userData = item.get("UserData")
|
||||
if(collectionType == "movies"):
|
||||
movie_count = movie_count + item.get("RecursiveItemCount")
|
||||
movie_unwatched_count = movie_unwatched_count + userData.get("UnplayedItemCount")
|
||||
|
||||
if(collectionType == "musicvideos"):
|
||||
musicvideos_count = musicvideos_count + item.get("RecursiveItemCount")
|
||||
musicvideos_unwatched_count = musicvideos_unwatched_count + userData.get("UnplayedItemCount")
|
||||
|
||||
if(collectionType == "tvshows"):
|
||||
tv_count = tv_count + item.get("ChildCount")
|
||||
episode_count = episode_count + item.get("RecursiveItemCount")
|
||||
episode_unwatched_count = episode_unwatched_count + userData.get("UnplayedItemCount")
|
||||
|
||||
if(collectionType == "music"):
|
||||
music_count = music_count + item.get("ChildCount")
|
||||
music_songs_count = music_songs_count + item.get("RecursiveItemCount")
|
||||
music_songs_unplayed_count = music_songs_unplayed_count + userData.get("UnplayedItemCount")
|
||||
|
||||
if(collectionType == "photos"):
|
||||
photos_count = photos_count + item.get("RecursiveItemCount")
|
||||
|
||||
if(item.get("Name") == "Trailers"):
|
||||
trailers_count = trailers_count + item.get("RecursiveItemCount")
|
||||
trailers_unwatched_count = trailers_unwatched_count + userData.get("UnplayedItemCount")
|
||||
|
||||
self.logMsg("MoviesCount " + str(movie_count), level=2)
|
||||
self.logMsg("MoviesUnWatchedCount " + str(movie_unwatched_count), level=2)
|
||||
self.logMsg("MusicVideosCount " + str(musicvideos_count), level=2)
|
||||
self.logMsg("MusicVideosUnWatchedCount " + str(musicvideos_unwatched_count), level=2)
|
||||
self.logMsg("TVCount " + str(tv_count), level=2)
|
||||
self.logMsg("EpisodeCount " + str(episode_count), level=2)
|
||||
self.logMsg("EpisodeUnWatchedCount " + str(episode_unwatched_count), level=2)
|
||||
self.logMsg("MusicCount " + str(music_count), level=2)
|
||||
self.logMsg("SongsCount " + str(music_songs_count), level=2)
|
||||
self.logMsg("SongsUnPlayedCount " + str(music_songs_unplayed_count), level=2)
|
||||
self.logMsg("TrailersCount" + str(trailers_count), level=2)
|
||||
self.logMsg("TrailersUnWatchedCount" + str(trailers_unwatched_count), level=2)
|
||||
self.logMsg("PhotosCount" + str(photos_count), level=2)
|
||||
|
||||
#item_count = item_count + 1
|
||||
|
||||
movie_watched_count = movie_count - movie_unwatched_count
|
||||
musicvideos_watched_count = musicvideos_count - musicvideos_unwatched_count
|
||||
episode_watched_count = episode_count - episode_unwatched_count
|
||||
music_songs_played_count = music_songs_count - music_songs_unplayed_count
|
||||
trailers_watched_count = trailers_count - trailers_unwatched_count
|
||||
WINDOW.setProperty("MB3TotalMovies", str(movie_count))
|
||||
WINDOW.setProperty("MB3TotalUnWatchedMovies", str(movie_unwatched_count))
|
||||
WINDOW.setProperty("MB3TotalWatchedMovies", str(movie_watched_count))
|
||||
WINDOW.setProperty("MB3TotalMusicVideos", str(musicvideos_count))
|
||||
WINDOW.setProperty("MB3TotalUnWatchedMusicVideos", str(musicvideos_unwatched_count))
|
||||
WINDOW.setProperty("MB3TotalWatchedMusicVideos", str(musicvideos_watched_count))
|
||||
WINDOW.setProperty("MB3TotalTvShows", str(tv_count))
|
||||
WINDOW.setProperty("MB3TotalEpisodes", str(episode_count))
|
||||
WINDOW.setProperty("MB3TotalUnWatchedEpisodes", str(episode_unwatched_count))
|
||||
WINDOW.setProperty("MB3TotalWatchedEpisodes", str(episode_watched_count))
|
||||
WINDOW.setProperty("MB3TotalMusicAlbums", str(music_count))
|
||||
WINDOW.setProperty("MB3TotalMusicSongs", str(music_songs_count))
|
||||
WINDOW.setProperty("MB3TotalUnPlayedMusicSongs", str(music_songs_unplayed_count))
|
||||
WINDOW.setProperty("MB3TotalPlayedMusicSongs", str(music_songs_played_count))
|
||||
WINDOW.setProperty("MB3TotalTrailers", str(trailers_count))
|
||||
WINDOW.setProperty("MB3TotalUnWatchedTrailers", str(trailers_unwatched_count))
|
||||
WINDOW.setProperty("MB3TotalWatchedTrailers", str(trailers_watched_count))
|
||||
WINDOW.setProperty("MB3TotalPhotos", str(photos_count))
|
||||
|
||||
userUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "?format=json"
|
||||
jsonData = downloadUtils.downloadUrl(userUrl, suppress=False, popup=1 )
|
||||
|
||||
result = json.loads(jsonData)
|
||||
userImage = downloadUtils.getUserArtwork(result, "Primary")
|
||||
WINDOW.setProperty("MB3UserImage", userImage)
|
||||
xbmc.log("XBMB3C MB3UserImage -> " + userImage)
|
||||
self.logMsg("InfoTV start")
|
||||
infoTVUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?&IncludeItemTypes=Series&Recursive=true&SeriesStatus=Continuing&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(infoTVUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("InfoTV Json Data : " + str(result), level=2)
|
||||
|
||||
totalRunning = result.get("TotalRecordCount")
|
||||
self.logMsg("TotalRunningCount " + str(totalRunning))
|
||||
WINDOW.setProperty("MB3TotalRunningTvShows", str(totalRunning))
|
||||
|
||||
self.logMsg("InfoNextAired start")
|
||||
InfoNextAiredUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?IsUnaired=true&SortBy=PremiereDate%2CAirTime%2CSortName&SortOrder=Ascending&IncludeItemTypes=Episode&Limit=1&Recursive=true&Fields=SeriesInfo%2CUserData&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(InfoNextAiredUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("InfoNextAired Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
episode = ""
|
||||
for item in result:
|
||||
title = ""
|
||||
seriesName = ""
|
||||
if(item.get("SeriesName") != None):
|
||||
seriesName = item.get("SeriesName").encode('utf-8')
|
||||
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
eppNumber = ""
|
||||
tempEpisodeNumber = ""
|
||||
if(item.get("IndexNumber") != None):
|
||||
eppNumber = item.get("IndexNumber")
|
||||
if eppNumber < 10:
|
||||
tempEpisodeNumber = "0" + str(eppNumber)
|
||||
else:
|
||||
tempEpisodeNumber = str(eppNumber)
|
||||
|
||||
seasonNumber = item.get("ParentIndexNumber")
|
||||
if seasonNumber < 10:
|
||||
tempSeasonNumber = "0" + str(seasonNumber)
|
||||
else:
|
||||
tempSeasonNumber = str(seasonNumber)
|
||||
|
||||
episode = seriesName + " - " + title + " - S" + tempSeasonNumber + "E" + tempEpisodeNumber
|
||||
|
||||
self.logMsg("MB3NextAiredEpisode" + episode)
|
||||
WINDOW.setProperty("MB3NextAiredEpisode", episode)
|
||||
self.logMsg("InfoNextAired end")
|
||||
|
||||
today = datetime.today()
|
||||
dateformat = today.strftime("%Y-%m-%d")
|
||||
nextAiredUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?IsUnaired=true&SortBy=PremiereDate%2CAirTime%2CSortName&SortOrder=Ascending&IncludeItemTypes=Episode&Recursive=true&Fields=SeriesInfo%2CUserData&MinPremiereDate=" + str(dateformat) + "&MaxPremiereDate=" + str(dateformat) + "&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(nextAiredUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("InfoNextAired total url: " + nextAiredUrl)
|
||||
self.logMsg("InfoNextAired total Json Data : " + str(result), level=2)
|
||||
|
||||
totalToday = result.get("TotalRecordCount")
|
||||
self.logMsg("MB3NextAiredTotalToday " + str(totalToday))
|
||||
WINDOW.setProperty("MB3NextAiredTotalToday", str(totalToday))
|
||||
|
||||
self.logMsg("Channels start")
|
||||
channelsUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Channels/?format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(channelsUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Channels Json Data : " + str(result), level=2)
|
||||
|
||||
totalChannels = result.get("TotalRecordCount")
|
||||
self.logMsg("TotalChannels " + str(totalRunning))
|
||||
WINDOW.setProperty("MB3TotalChannels", str(totalChannels))
|
227
resources/lib/ItemInfo.py
Normal file
227
resources/lib/ItemInfo.py
Normal file
@ -0,0 +1,227 @@
|
||||
|
||||
import sys
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import json as json
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
_MODE_CAST_LIST=14
|
||||
_MODE_PERSON_DETAILS=15
|
||||
|
||||
class ItemInfo(xbmcgui.WindowXMLDialog):
|
||||
|
||||
id = ""
|
||||
playUrl = ""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
||||
xbmc.log("WINDOW INITIALISED")
|
||||
|
||||
def onInit(self):
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
__settings__ = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
port = __settings__.getSetting('port')
|
||||
host = __settings__.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
|
||||
jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + self.id + "?format=json", suppress=False, popup=1 )
|
||||
item = json.loads(jsonData)
|
||||
|
||||
id = item.get("Id")
|
||||
name = item.get("Name")
|
||||
image = downloadUtils.getArtwork(item, "Primary")
|
||||
fanArt = downloadUtils.getArtwork(item, "Backdrop")
|
||||
|
||||
# calculate the percentage complete
|
||||
userData = item.get("UserData")
|
||||
cappedPercentage = None
|
||||
if(userData != None):
|
||||
playBackTicks = float(userData.get("PlaybackPositionTicks"))
|
||||
if(playBackTicks != None and playBackTicks > 0):
|
||||
runTimeTicks = float(item.get("RunTimeTicks", "0"))
|
||||
if(runTimeTicks > 0):
|
||||
percentage = int((playBackTicks / runTimeTicks) * 100.0)
|
||||
cappedPercentage = percentage - (percentage % 10)
|
||||
if(cappedPercentage == 0):
|
||||
cappedPercentage = 10
|
||||
if(cappedPercentage == 100):
|
||||
cappedPercentage = 90
|
||||
|
||||
episodeInfo = ""
|
||||
type = item.get("Type")
|
||||
if(type == "Episode" or type == "Season"):
|
||||
name = item.get("SeriesName") + ": " + name
|
||||
season = str(item.get("ParentIndexNumber")).zfill(2)
|
||||
episodeNum = str(item.get("IndexNumber")).zfill(2)
|
||||
episodeInfo = "S" + season + "xE" + episodeNum
|
||||
|
||||
url = server + ',;' + id
|
||||
url = urllib.quote(url)
|
||||
self.playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
|
||||
self.peopleUrl = "XBMC.Container.Update(plugin://plugin.video.xbmb3c?mode=" + str(_MODE_CAST_LIST) + "&id=" + id + ")"
|
||||
#self.peopleUrl = "XBMC.RunPlugin(plugin://plugin.video.xbmb3c?mode=" + str(_MODE_CAST_LIST) + "&id=" + id + ")"
|
||||
|
||||
# all all the media stream info
|
||||
mediaList = self.getControl(3220)
|
||||
|
||||
mediaStreams = item.get("MediaStreams")
|
||||
if(mediaStreams != None):
|
||||
for mediaStream in mediaStreams:
|
||||
if(mediaStream.get("Type") == "Video"):
|
||||
videocodec = mediaStream.get("Codec")
|
||||
if(videocodec == "mpeg2video"):
|
||||
videocodec = "mpeg2"
|
||||
height = str(mediaStream.get("Height"))
|
||||
width = str(mediaStream.get("Width"))
|
||||
aspectratio = mediaStream.get("AspectRatio")
|
||||
fr = mediaStream.get("RealFrameRate")
|
||||
videoInfo = width + "x" + height + " " + videocodec + " " + str(round(fr, 2))
|
||||
listItem = xbmcgui.ListItem("Video:", videoInfo)
|
||||
mediaList.addItem(listItem)
|
||||
if(mediaStream.get("Type") == "Audio"):
|
||||
audiocodec = mediaStream.get("Codec")
|
||||
channels = mediaStream.get("Channels")
|
||||
lang = mediaStream.get("Language")
|
||||
audioInfo = audiocodec + " " + str(channels)
|
||||
if(lang != None and len(lang) > 0 and lang != "und"):
|
||||
audioInfo = audioInfo + " " + lang
|
||||
listItem = xbmcgui.ListItem("Audio:", audioInfo)
|
||||
mediaList.addItem(listItem)
|
||||
if(mediaStream.get("Type") == "Subtitle"):
|
||||
lang = mediaStream.get("Language")
|
||||
codec = mediaStream.get("Codec")
|
||||
subInfo = codec
|
||||
if(lang != None and len(lang) > 0 and lang != "und"):
|
||||
subInfo = subInfo + " " + lang
|
||||
listItem = xbmcgui.ListItem("Sub:", subInfo)
|
||||
mediaList.addItem(listItem)
|
||||
|
||||
|
||||
#for x in range(0, 10):
|
||||
# listItem = xbmcgui.ListItem("Test:", "Test 02 " + str(x))
|
||||
# mediaList.addItem(listItem)
|
||||
|
||||
# add overview
|
||||
overview = item.get("Overview")
|
||||
self.getControl(3223).setText(overview)
|
||||
|
||||
# add people
|
||||
peopleList = self.getControl(3230)
|
||||
people = item.get("People")
|
||||
|
||||
for person in people:
|
||||
displayName = person.get("Name")
|
||||
role = person.get("Role")
|
||||
id = person.get("Id")
|
||||
tag = person.get("PrimaryImageTag")
|
||||
|
||||
baseName = person.get("Name")
|
||||
baseName = baseName.replace(" ", "+")
|
||||
baseName = baseName.replace("&", "_")
|
||||
baseName = baseName.replace("?", "_")
|
||||
baseName = baseName.replace("=", "_")
|
||||
|
||||
actionUrl = "plugin://plugin.video.xbmb3c?mode=" + str(_MODE_PERSON_DETAILS) +"&name=" + baseName
|
||||
|
||||
if(tag != None and len(tag) > 0):
|
||||
thumbPath = downloadUtils.imageUrl(id, "Primary", 0, 400, 400)
|
||||
listItem = xbmcgui.ListItem(label=displayName, label2=role, iconImage=thumbPath, thumbnailImage=thumbPath)
|
||||
else:
|
||||
listItem = xbmcgui.ListItem(label=displayName, label2=role)
|
||||
|
||||
listItem.setProperty("ActionUrl", actionUrl)
|
||||
peopleList.addItem(listItem)
|
||||
|
||||
# add general info
|
||||
infoList = self.getControl(3226)
|
||||
listItem = xbmcgui.ListItem("Year:", str(item.get("ProductionYear")))
|
||||
infoList.addItem(listItem)
|
||||
listItem = xbmcgui.ListItem("Rating:", str(item.get("CommunityRating")))
|
||||
infoList.addItem(listItem)
|
||||
listItem = xbmcgui.ListItem("MPAA:", str(item.get("OfficialRating")))
|
||||
infoList.addItem(listItem)
|
||||
duration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60))
|
||||
listItem = xbmcgui.ListItem("RunTime:", str(duration) + " Minutes")
|
||||
infoList.addItem(listItem)
|
||||
|
||||
genre = ""
|
||||
genres = item.get("Genres")
|
||||
if(genres != None):
|
||||
for genre_string in genres:
|
||||
if genre == "": #Just take the first genre
|
||||
genre = genre_string
|
||||
else:
|
||||
genre = genre + " / " + genre_string
|
||||
|
||||
listItem = xbmcgui.ListItem("Genre:", genre)
|
||||
infoList.addItem(listItem)
|
||||
|
||||
path = item.get('Path')
|
||||
listItem = xbmcgui.ListItem("Path:", path)
|
||||
infoList.addItem(listItem)
|
||||
|
||||
# add resume percentage text to name
|
||||
addResumePercent = __settings__.getSetting('addResumePercent') == 'true'
|
||||
if (addResumePercent and cappedPercentage != None):
|
||||
name = name + " (" + str(cappedPercentage) + "%)"
|
||||
|
||||
self.getControl(3000).setLabel(name)
|
||||
self.getControl(3003).setLabel(episodeInfo)
|
||||
self.getControl(3001).setImage(fanArt)
|
||||
|
||||
if(type == "Episode"):
|
||||
self.getControl(3009).setImage(image)
|
||||
if(cappedPercentage != None):
|
||||
self.getControl(3010).setImage("Progress\progress_" + str(cappedPercentage) + ".png")
|
||||
else:
|
||||
self.getControl(3011).setImage(image)
|
||||
if(cappedPercentage != None):
|
||||
self.getControl(3012).setImage("Progress\progress_" + str(cappedPercentage) + ".png")
|
||||
|
||||
# disable play button
|
||||
if(type == "Season" or type == "Series"):
|
||||
self.setFocusId(3226)
|
||||
self.getControl(3002).setEnabled(False)
|
||||
|
||||
def setId(self, id):
|
||||
self.id = id
|
||||
|
||||
def onFocus(self, controlId):
|
||||
pass
|
||||
|
||||
def doAction(self):
|
||||
pass
|
||||
|
||||
def closeDialog(self):
|
||||
self.close()
|
||||
|
||||
def onClick(self, controlID):
|
||||
|
||||
if(controlID == 3002):
|
||||
|
||||
# close all dialogs when playing an item
|
||||
xbmc.executebuiltin("Dialog.Close(all,true)")
|
||||
|
||||
xbmc.executebuiltin("RunPlugin(" + self.playUrl + ")")
|
||||
self.close()
|
||||
|
||||
elif(controlID == 3230):
|
||||
|
||||
peopleList = self.getControl(3230)
|
||||
item = peopleList.getSelectedItem()
|
||||
action = item.getProperty("ActionUrl")
|
||||
|
||||
xbmc.log(action)
|
||||
xbmc.executebuiltin("RunPlugin(" + action + ")")
|
||||
|
||||
pass
|
||||
|
116
resources/lib/MenuLoad.py
Normal file
116
resources/lib/MenuLoad.py
Normal file
@ -0,0 +1,116 @@
|
||||
#################################################################################################
|
||||
# menu item loader thread
|
||||
# this loads the favourites.xml and sets the windows props for the menus to auto display in skins
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import xml.etree.ElementTree as xml
|
||||
import os
|
||||
import threading
|
||||
|
||||
class LoadMenuOptionsThread(threading.Thread):
|
||||
|
||||
logLevel = 0
|
||||
addonSettings = None
|
||||
getString = None
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
self.getString = self.addonSettings.getLocalizedString
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C LoadMenuOptionsThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C LoadMenuOptionsThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.run_internal()
|
||||
except Exception, e:
|
||||
xbmcgui.Dialog().ok(self.getString(30205), str(e))
|
||||
raise
|
||||
|
||||
def run_internal(self):
|
||||
self.logMsg("LoadMenuOptionsThread Started")
|
||||
|
||||
lastFavPath = ""
|
||||
favourites_file = os.path.join(xbmc.translatePath('special://profile'), "favourites.xml")
|
||||
self.loadMenuOptions(favourites_file)
|
||||
lastFavPath = favourites_file
|
||||
|
||||
try:
|
||||
lastModLast = os.stat(favourites_file).st_mtime
|
||||
except:
|
||||
lastModLast = 0;
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
|
||||
favourites_file = os.path.join(xbmc.translatePath('special://profile'), "favourites.xml")
|
||||
try:
|
||||
lastMod = os.stat(favourites_file).st_mtime
|
||||
except:
|
||||
lastMod = 0;
|
||||
|
||||
if(lastFavPath != favourites_file or lastModLast != lastMod):
|
||||
self.loadMenuOptions(favourites_file)
|
||||
|
||||
lastFavPath = favourites_file
|
||||
lastModLast = lastMod
|
||||
|
||||
xbmc.sleep(3000)
|
||||
|
||||
self.logMsg("LoadMenuOptionsThread Exited")
|
||||
|
||||
def loadMenuOptions(self, pathTofavourites):
|
||||
|
||||
self.logMsg("LoadMenuOptionsThread -> Loading menu items from : " + pathTofavourites)
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
menuItem = 0
|
||||
|
||||
try:
|
||||
tree = xml.parse(pathTofavourites)
|
||||
rootElement = tree.getroot()
|
||||
except Exception, e:
|
||||
self.logMsg("LoadMenuOptionsThread -> Error Parsing favourites.xml : " + str(e), level=0)
|
||||
for x in range(0, 10):
|
||||
WINDOW.setProperty("xbmb3c_menuitem_name_" + str(x), "")
|
||||
WINDOW.setProperty("xbmb3c_menuitem_action_" + str(x), "")
|
||||
WINDOW.setProperty("xbmb3c_menuitem_collection_" + str(x), "")
|
||||
return
|
||||
|
||||
for child in rootElement.findall('favourite'):
|
||||
name = child.get('name')
|
||||
action = child.text
|
||||
|
||||
if(len(name) > 1 and name[0:1] != '-'):
|
||||
WINDOW.setProperty("xbmb3c_menuitem_name_" + str(menuItem), name)
|
||||
WINDOW.setProperty("xbmb3c_menuitem_action_" + str(menuItem), action)
|
||||
WINDOW.setProperty("xbmb3c_menuitem_collection_" + str(menuItem), name)
|
||||
self.logMsg("xbmb3c_menuitem_name_" + str(menuItem) + " : " + name)
|
||||
self.logMsg("xbmb3c_menuitem_action_" + str(menuItem) + " : " + action)
|
||||
self.logMsg("xbmb3c_menuitem_collection_" + str(menuItem) + " : " + name)
|
||||
|
||||
menuItem = menuItem + 1
|
||||
|
||||
for x in range(menuItem, menuItem+10):
|
||||
WINDOW.setProperty("xbmb3c_menuitem_name_" + str(x), "")
|
||||
WINDOW.setProperty("xbmb3c_menuitem_action_" + str(x), "")
|
||||
self.logMsg("xbmb3c_menuitem_name_" + str(x) + " : ")
|
||||
self.logMsg("xbmb3c_menuitem_action_" + str(x) + " : ")
|
||||
self.logMsg("xbmb3c_menuitem_collection_" + str(x) + " : ")
|
||||
|
||||
|
||||
|
||||
|
||||
|
196
resources/lib/NextUpItems.py
Normal file
196
resources/lib/NextUpItems.py
Normal file
@ -0,0 +1,196 @@
|
||||
#################################################################################################
|
||||
# NextUp TV Updater
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
class NextUpUpdaterThread(threading.Thread):
|
||||
|
||||
logLevel = 0
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C NextUpUpdaterThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C NextUpUpdaterThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
self.logMsg("Started")
|
||||
|
||||
self.updateNextUp()
|
||||
lastRun = datetime.today()
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
td = datetime.today() - lastRun
|
||||
secTotal = td.seconds
|
||||
|
||||
if(secTotal > 300):
|
||||
self.updateNextUp()
|
||||
lastRun = datetime.today()
|
||||
|
||||
xbmc.sleep(3000)
|
||||
|
||||
self.logMsg("Exited")
|
||||
|
||||
def updateNextUp(self):
|
||||
self.logMsg("updateNextUp Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
self.logMsg("updateNextUp UserID : " + userid)
|
||||
|
||||
self.logMsg("Updating NextUp List")
|
||||
|
||||
nextUpUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Shows/NextUp?UserId=" + userid + "&Fields=Path,Genres,MediaStreams,Overview&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(nextUpUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("NextUP TV Show Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
seriesName = "Missing Name"
|
||||
if(item.get("SeriesName") != None):
|
||||
seriesName = item.get("SeriesName").encode('utf-8')
|
||||
|
||||
eppNumber = "X"
|
||||
tempEpisodeNumber = "XX"
|
||||
if(item.get("IndexNumber") != None):
|
||||
eppNumber = item.get("IndexNumber")
|
||||
if eppNumber < 10:
|
||||
tempEpisodeNumber = "0" + str(eppNumber)
|
||||
else:
|
||||
tempEpisodeNumber = str(eppNumber)
|
||||
|
||||
seasonNumber = item.get("ParentIndexNumber")
|
||||
tempSeasonNumber = "XX"
|
||||
if seasonNumber < 10:
|
||||
tempSeasonNumber = "0" + str(seasonNumber)
|
||||
else:
|
||||
tempSeasonNumber = str(seasonNumber)
|
||||
rating = str(item.get("CommunityRating"))
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
poster = downloadUtils.getArtwork(item, "SeriesPrimary")
|
||||
small_poster = downloadUtils.getArtwork(item, "Primary2")
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
medium_fanart = downloadUtils.getArtwork(item, "Backdrop3")
|
||||
banner = downloadUtils.getArtwork(item, "Banner")
|
||||
if item.get("SeriesThumbImageTag") != None:
|
||||
seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
seriesthumbnail = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
# Process UserData
|
||||
userData = item.get("UserData")
|
||||
if(userData != None):
|
||||
resume = str(userData.get("PlaybackPositionTicks"))
|
||||
if (resume == "0"):
|
||||
resume = "False"
|
||||
else:
|
||||
resume = "True"
|
||||
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
self.logMsg("NextUpEpisodeMB3." + str(item_count) + ".Resume = " + resume, level=2)
|
||||
|
||||
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".EpisodeTitle", title)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Rating", rating)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.medium_fanart)", medium_fanart)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Art(tvshow.small_poster)", small_poster)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Plot", plot)
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(item_count) + ".Resume", resume)
|
||||
|
||||
WINDOW.setProperty("NextUpEpisodeMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
if(item_count < 10):
|
||||
# blank any not available
|
||||
for x in range(item_count, 11):
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".EpisodeTitle", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".ShowTitle", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".EpisodeNo", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".SeasonNo", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Thumb", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Path", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Rating", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Art(tvshow.fanart)", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Art(tvshow.clearlogo)", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Art(tvshow.banner)", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Art(tvshow.poster)", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Plot", "")
|
||||
WINDOW.setProperty("NextUpEpisodeMB3." + str(x) + ".Resume", "")
|
||||
|
||||
|
||||
|
||||
|
175
resources/lib/PersonInfo.py
Normal file
175
resources/lib/PersonInfo.py
Normal file
@ -0,0 +1,175 @@
|
||||
|
||||
import sys
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import json as json
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_GETCONTENT=0
|
||||
_MODE_ITEM_DETAILS=17
|
||||
|
||||
class PersonInfo(xbmcgui.WindowXMLDialog):
|
||||
|
||||
pluginCastLink = ""
|
||||
showMovies = False
|
||||
personName = ""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
||||
|
||||
def onInit(self):
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
__settings__ = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
port = __settings__.getSetting('port')
|
||||
host = __settings__.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
|
||||
jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Persons/" + self.personName + "?format=json", suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
name = result.get("Name")
|
||||
id = result.get("Id")
|
||||
|
||||
# other lib items count
|
||||
contentCounts = ""
|
||||
if(result.get("AdultVideoCount") != None and result.get("AdultVideoCount") > 0):
|
||||
contentCounts = contentCounts + "\nAdult Count : " + str(result.get("AdultVideoCount"))
|
||||
if(result.get("MovieCount") != None and result.get("MovieCount") > 0):
|
||||
contentCounts = contentCounts + "\nMovie Count : " + str(result.get("MovieCount"))
|
||||
if(result.get("SeriesCount") != None and result.get("SeriesCount") > 0):
|
||||
contentCounts = contentCounts + "\nSeries Count : " + str(result.get("SeriesCount"))
|
||||
if(result.get("EpisodeCount") != None and result.get("EpisodeCount") > 0):
|
||||
contentCounts = contentCounts + "\nEpisode Count : " + str(result.get("EpisodeCount"))
|
||||
|
||||
if(len(contentCounts) > 0):
|
||||
contentCounts = "Total Library Counts:" + contentCounts
|
||||
|
||||
#overview
|
||||
overview = ""
|
||||
if(len(contentCounts) > 0):
|
||||
overview = contentCounts + "\n\n"
|
||||
over = result.get("Overview")
|
||||
if(over == None or over == ""):
|
||||
overview = overview + "No details available"
|
||||
else:
|
||||
overview = overview + over
|
||||
|
||||
#person image
|
||||
image = downloadUtils.getArtwork(result, "Primary")
|
||||
|
||||
#get other movies
|
||||
encoded = name.encode("utf-8")
|
||||
encoded = urllib.quote(encoded)
|
||||
url = "http://" + server + "/mediabrowser/Users/" + userid + "/Items/?Recursive=True&Person=" + encoded + "&format=json"
|
||||
xbmc.log("URL: " + url)
|
||||
jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1 )
|
||||
otherMovieResult = json.loads(jsonData)
|
||||
|
||||
baseName = name.replace(" ", "+")
|
||||
baseName = baseName.replace("&", "_")
|
||||
baseName = baseName.replace("?", "_")
|
||||
baseName = baseName.replace("=", "_")
|
||||
|
||||
#detailsString = getDetailsString()
|
||||
#search_url = "http://" + host + ":" + port + "/mediabrowser/Users/" + userid + "/Items/?Recursive=True&Person=PERSON_NAME&Fields=" + detailsString + "&format=json"
|
||||
search_url = "http://" + host + ":" + port + "/mediabrowser/Users/" + userid + "/Items/?Recursive=True&Person=PERSON_NAME&format=json"
|
||||
search_url = urllib.quote(search_url)
|
||||
search_url = search_url.replace("PERSON_NAME", baseName)
|
||||
self.pluginCastLink = "XBMC.Container.Update(plugin://plugin.video.xbmb3c?mode=" + str(_MODE_GETCONTENT) + "&url=" + search_url + ")"
|
||||
|
||||
otherItemsList = None
|
||||
try:
|
||||
otherItemsList = self.getControl(3010)
|
||||
|
||||
items = otherMovieResult.get("Items")
|
||||
if(items == None):
|
||||
items = []
|
||||
|
||||
for item in items:
|
||||
item_id = item.get("Id")
|
||||
item_name = item.get("Name")
|
||||
|
||||
type_info = ""
|
||||
image_id = item_id
|
||||
item_type = item.get("Type")
|
||||
|
||||
if(item_type == "Season"):
|
||||
image_id = item.get("SeriesId")
|
||||
season = item.get("IndexNumber")
|
||||
type_info = "Season " + str(season).zfill(2)
|
||||
elif(item_type == "Series"):
|
||||
image_id = item.get("Id")
|
||||
type_info = "Series"
|
||||
elif(item_type == "Movie"):
|
||||
image_id = item.get("Id")
|
||||
type_info = "Movie"
|
||||
elif(item_type == "Episode"):
|
||||
image_id = item.get("SeriesId")
|
||||
season = item.get("ParentIndexNumber")
|
||||
eppNum = item.get("IndexNumber")
|
||||
type_info = "S" + str(season).zfill(2) + "E" + str(eppNum).zfill(2)
|
||||
|
||||
thumbPath = downloadUtils.imageUrl(image_id, "Primary", 0, 200, 200)
|
||||
|
||||
listItem = xbmcgui.ListItem(label=item_name, label2=type_info, iconImage=thumbPath, thumbnailImage=thumbPath)
|
||||
|
||||
actionUrl = "plugin://plugin.video.xbmb3c?id=" + item_id + "&mode=" + str(_MODE_ITEM_DETAILS)
|
||||
listItem.setProperty("ActionUrl", actionUrl)
|
||||
|
||||
otherItemsList.addItem(listItem)
|
||||
|
||||
except Exception, e:
|
||||
xbmc.log("Exception : " + str(e))
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# set the dialog data
|
||||
self.getControl(3000).setLabel(name)
|
||||
self.getControl(3001).setText(overview)
|
||||
self.getControl(3009).setImage(image)
|
||||
|
||||
def setPersonName(self, name):
|
||||
self.personName = name
|
||||
|
||||
def setInfo(self, data):
|
||||
self.details = data
|
||||
|
||||
def onFocus(self, controlId):
|
||||
pass
|
||||
|
||||
def doAction(self):
|
||||
pass
|
||||
|
||||
def closeDialog(self):
|
||||
self.close()
|
||||
|
||||
def onClick(self, controlID):
|
||||
|
||||
if(controlID == 3002):
|
||||
self.showMovies = True
|
||||
|
||||
xbmc.executebuiltin('Dialog.Close(movieinformation)')
|
||||
self.close()
|
||||
|
||||
elif(controlID == 3010):
|
||||
|
||||
#xbmc.executebuiltin("Dialog.Close(all,true)")
|
||||
|
||||
itemList = self.getControl(3010)
|
||||
item = itemList.getSelectedItem()
|
||||
action = item.getProperty("ActionUrl")
|
||||
|
||||
xbmc.executebuiltin("RunPlugin(" + action + ")")
|
||||
|
||||
self.close()
|
||||
|
||||
pass
|
||||
|
319
resources/lib/RandomItems.py
Normal file
319
resources/lib/RandomItems.py
Normal file
@ -0,0 +1,319 @@
|
||||
#################################################################################################
|
||||
# Random Info Updater
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
class RandomInfoUpdaterThread(threading.Thread):
|
||||
|
||||
logLevel = 0
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C RandomInfoUpdaterThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C RandomInfoUpdaterThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
self.logMsg("Started")
|
||||
|
||||
self.updateRandom()
|
||||
lastRun = datetime.today()
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
td = datetime.today() - lastRun
|
||||
secTotal = td.seconds
|
||||
|
||||
if(secTotal > 300):
|
||||
self.updateRandom()
|
||||
lastRun = datetime.today()
|
||||
|
||||
xbmc.sleep(3000)
|
||||
|
||||
self.logMsg("Exited")
|
||||
|
||||
def updateRandom(self):
|
||||
self.logMsg("updateRandomMovies Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
self.logMsg("updateRandomMovies UserID : " + userid)
|
||||
|
||||
self.logMsg("Updating Random Movie List")
|
||||
|
||||
randomUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=Random&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IncludeItemTypes=Movie&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(randomUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Random Movie Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
rating = item.get("CommunityRating")
|
||||
criticrating = item.get("CriticRating")
|
||||
officialrating = item.get("OfficialRating")
|
||||
criticratingsummary = ""
|
||||
if(item.get("CriticRatingSummary") != None):
|
||||
criticratingsummary = item.get("CriticRatingSummary").encode('utf-8')
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
year = item.get("ProductionYear")
|
||||
if(item.get("RunTimeTicks") != None):
|
||||
runtime = str(int(item.get("RunTimeTicks"))/(10000000*60))
|
||||
else:
|
||||
runtime = "0"
|
||||
|
||||
item_id = item.get("Id")
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
medium_fanart = downloadUtils.getArtwork(item, "Backdrop3")
|
||||
if (item.get("ImageTags") != None and item.get("ImageTags").get("Thumb") != None):
|
||||
realthumb = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
realthumb = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Title = " + title, level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Year = " + str(year), level=2)
|
||||
self.logMsg("RandomMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2)
|
||||
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Title", title)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Art(fanart)", fanart)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Art(medium_fanart)", medium_fanart)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Art(clearlogo)", logo)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Art(poster)", thumbnail)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".RealThumb", realthumb)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Rating", str(rating))
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Mpaa", str(officialrating))
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".CriticRating", str(criticrating))
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Plot", plot)
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Year", str(year))
|
||||
WINDOW.setProperty("RandomMovieMB3." + str(item_count) + ".Runtime", str(runtime))
|
||||
|
||||
WINDOW.setProperty("RandomMovieMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
self.logMsg("Updating Random TV Show List")
|
||||
|
||||
randomUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=10&Recursive=true&SortBy=Random&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=Episode&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(randomUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Random TV Show Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
seriesName = "Missing Name"
|
||||
if(item.get("SeriesName") != None):
|
||||
seriesName = item.get("SeriesName").encode('utf-8')
|
||||
|
||||
eppNumber = "X"
|
||||
tempEpisodeNumber = ""
|
||||
if(item.get("IndexNumber") != None):
|
||||
eppNumber = item.get("IndexNumber")
|
||||
if eppNumber < 10:
|
||||
tempEpisodeNumber = "0" + str(eppNumber)
|
||||
else:
|
||||
tempEpisodeNumber = str(eppNumber)
|
||||
|
||||
seasonNumber = item.get("ParentIndexNumber")
|
||||
if seasonNumber < 10:
|
||||
tempSeasonNumber = "0" + str(seasonNumber)
|
||||
else:
|
||||
tempSeasonNumber = str(seasonNumber)
|
||||
rating = str(item.get("CommunityRating"))
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
poster = downloadUtils.getArtwork(item, "SeriesPrimary")
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
medium_fanart = downloadUtils.getArtwork(item, "Backdrop3")
|
||||
banner = downloadUtils.getArtwork(item, "Banner")
|
||||
if item.get("SeriesThumbImageTag") != None:
|
||||
seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
seriesthumbnail = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2)
|
||||
self.logMsg("RandomEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
|
||||
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".EpisodeTitle", title)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Rating", rating)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.medium_fanart)", medium_fanart)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster)
|
||||
WINDOW.setProperty("RandomEpisodeMB3." + str(item_count) + ".Plot", plot)
|
||||
|
||||
WINDOW.setProperty("RandomEpisodeMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
# update random music
|
||||
self.logMsg("Updating Random MusicList")
|
||||
|
||||
randomUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=Random&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed,IsFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=MusicAlbum&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(randomUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Random MusicList Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
artist = "Missing Artist"
|
||||
if(item.get("AlbumArtist") != None):
|
||||
artist = item.get("AlbumArtist").encode('utf-8')
|
||||
|
||||
year = "0000"
|
||||
if(item.get("ProductionYear") != None):
|
||||
year = str(item.get("ProductionYear"))
|
||||
plot = "Missing Plot"
|
||||
if(item.get("Overview") != None):
|
||||
plot = item.get("Overview").encode('utf-8')
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
if item.get("Type") == "MusicAlbum":
|
||||
parentId = item.get("ParentLogoItemId")
|
||||
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
banner = downloadUtils.getArtwork(item, "Banner")
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Title = " + title, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Artist = " + artist, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Year = " + year, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Art(banner) = " + banner, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2)
|
||||
self.logMsg("RandomAlbumMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
|
||||
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Title", title)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Artist", artist)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Year", year)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Rating", rating)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Art(fanart)", fanart)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Art(clearlogo)", logo)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Art(banner)", banner)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Art(poster)", thumbnail)
|
||||
WINDOW.setProperty("RandomAlbumMB3." + str(item_count) + ".Plot", plot)
|
||||
|
||||
WINDOW.setProperty("RandomAlbumMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
|
||||
|
564
resources/lib/RecentItems.py
Normal file
564
resources/lib/RecentItems.py
Normal file
@ -0,0 +1,564 @@
|
||||
#################################################################################################
|
||||
# Recent Info Updater
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
|
||||
class RecentInfoUpdaterThread(threading.Thread):
|
||||
|
||||
logLevel = 0
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C RecentInfoUpdaterThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C RecentInfoUpdaterThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
self.logMsg("Started")
|
||||
|
||||
self.updateRecent()
|
||||
lastRun = datetime.today()
|
||||
lastProfilePath = xbmc.translatePath('special://profile')
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
td = datetime.today() - lastRun
|
||||
secTotal = td.seconds
|
||||
|
||||
profilePath = xbmc.translatePath('special://profile')
|
||||
|
||||
updateInterval = 60
|
||||
if (xbmc.Player().isPlaying()):
|
||||
updateInterval = 300
|
||||
|
||||
if(secTotal > updateInterval or lastProfilePath != profilePath):
|
||||
self.updateRecent()
|
||||
lastRun = datetime.today()
|
||||
|
||||
lastProfilePath = profilePath
|
||||
|
||||
xbmc.sleep(3000)
|
||||
|
||||
self.logMsg("Exited")
|
||||
|
||||
def updateRecent(self):
|
||||
self.logMsg("updateRecent Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
|
||||
self.logMsg("UserName : " + userName + " UserID : " + userid)
|
||||
|
||||
self.logMsg("Updating Recent Movie List")
|
||||
|
||||
recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IncludeItemTypes=Movie&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Recent Movie Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
rating = item.get("CommunityRating")
|
||||
criticrating = item.get("CriticRating")
|
||||
officialrating = item.get("OfficialRating")
|
||||
criticratingsummary = ""
|
||||
if(item.get("CriticRatingSummary") != None):
|
||||
criticratingsummary = item.get("CriticRatingSummary").encode('utf-8')
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
year = item.get("ProductionYear")
|
||||
if(item.get("RunTimeTicks") != None):
|
||||
runtime = str(int(item.get("RunTimeTicks"))/(10000000*60))
|
||||
else:
|
||||
runtime = "0"
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Title = " + title, level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Year = " + str(year), level=2)
|
||||
self.logMsg("LatestMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2)
|
||||
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Title", title)
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Art(fanart)", fanart)
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Art(clearlogo)", logo)
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Art(poster)", thumbnail)
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Rating", str(rating))
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Mpaa", str(officialrating))
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".CriticRating", str(criticrating))
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary)
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Plot", plot)
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Year", str(year))
|
||||
WINDOW.setProperty("LatestMovieMB3." + str(item_count) + ".Runtime", str(runtime))
|
||||
|
||||
WINDOW.setProperty("LatestMovieMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
#Updating Recent Unplayed Movie List
|
||||
self.logMsg("Updating Recent Unplayed Movie List")
|
||||
|
||||
recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/Latest?Limit=30&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview,CriticRatingSummary&IsPlayed=false&IncludeItemTypes=Movie&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Recent Unplayed Movie Json Data : " + str(result), level=2)
|
||||
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
rating = item.get("CommunityRating")
|
||||
criticrating = item.get("CriticRating")
|
||||
officialrating = item.get("OfficialRating")
|
||||
criticratingsummary = ""
|
||||
if(item.get("CriticRatingSummary") != None):
|
||||
criticratingsummary = item.get("CriticRatingSummary").encode('utf-8')
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
year = item.get("ProductionYear")
|
||||
if(item.get("RunTimeTicks") != None):
|
||||
runtime = str(int(item.get("RunTimeTicks"))/(10000000*60))
|
||||
else:
|
||||
runtime = "0"
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
medium_fanart = downloadUtils.getArtwork(item, "Backdrop3")
|
||||
|
||||
if (item.get("ImageTags") != None and item.get("ImageTags").get("Thumb") != None):
|
||||
realthumb = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
realthumb = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Title = " + title, level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Year = " + str(year), level=2)
|
||||
self.logMsg("LatestUnplayedMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2)
|
||||
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Title", title)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Art(fanart)", fanart)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Art(medium_fanart)", medium_fanart)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Art(clearlogo)", logo)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Art(poster)", thumbnail)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".RealThumb", realthumb)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Rating", str(rating))
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Mpaa", str(officialrating))
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".CriticRating", str(criticrating))
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Plot", plot)
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Year", str(year))
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3." + str(item_count) + ".Runtime", str(runtime))
|
||||
|
||||
WINDOW.setProperty("LatestUnplayedMovieMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
#Updating Recent TV Show List
|
||||
self.logMsg("Updating Recent TV Show List")
|
||||
|
||||
recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=30&Recursive=true&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed,IsNotFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=Episode&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Recent TV Show Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
seriesName = "Missing Name"
|
||||
if(item.get("SeriesName") != None):
|
||||
seriesName = item.get("SeriesName").encode('utf-8')
|
||||
|
||||
eppNumber = "X"
|
||||
tempEpisodeNumber = "00"
|
||||
if(item.get("IndexNumber") != None):
|
||||
eppNumber = item.get("IndexNumber")
|
||||
if eppNumber < 10:
|
||||
tempEpisodeNumber = "0" + str(eppNumber)
|
||||
else:
|
||||
tempEpisodeNumber = str(eppNumber)
|
||||
|
||||
seasonNumber = item.get("ParentIndexNumber")
|
||||
if seasonNumber < 10:
|
||||
tempSeasonNumber = "0" + str(seasonNumber)
|
||||
else:
|
||||
tempSeasonNumber = str(seasonNumber)
|
||||
rating = str(item.get("CommunityRating"))
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
if item.get("Type") == "Episode" or item.get("Type") == "Season":
|
||||
series_id = item.get("SeriesId")
|
||||
|
||||
poster = downloadUtils.getArtwork(item, "SeriesPrimary")
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
banner = downloadUtils.getArtwork(item, "Banner")
|
||||
if item.get("SeriesThumbImageTag") != None:
|
||||
seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
seriesthumbnail = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2)
|
||||
self.logMsg("LatestEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".EpisodeTitle", title)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Rating", rating)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster)
|
||||
WINDOW.setProperty("LatestEpisodeMB3." + str(item_count) + ".Plot", plot)
|
||||
|
||||
WINDOW.setProperty("LatestEpisodeMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
#Updating Recent Unplayed TV Show List
|
||||
self.logMsg("Updating Recent Unplayed TV Show List")
|
||||
|
||||
recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items/Latest?Limit=30&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview&IsPlayed=false&GroupItems=false&IncludeItemTypes=Episode&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Recent Unplayed TV Show Json Data : " + str(result), level=2)
|
||||
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
seriesName = "Missing Name"
|
||||
if(item.get("SeriesName") != None):
|
||||
seriesName = item.get("SeriesName").encode('utf-8')
|
||||
|
||||
eppNumber = "X"
|
||||
tempEpisodeNumber = "00"
|
||||
if(item.get("IndexNumber") != None):
|
||||
eppNumber = item.get("IndexNumber")
|
||||
if eppNumber < 10:
|
||||
tempEpisodeNumber = "0" + str(eppNumber)
|
||||
else:
|
||||
tempEpisodeNumber = str(eppNumber)
|
||||
|
||||
seasonNumber = item.get("ParentIndexNumber")
|
||||
if seasonNumber < 10:
|
||||
tempSeasonNumber = "0" + str(seasonNumber)
|
||||
else:
|
||||
tempSeasonNumber = str(seasonNumber)
|
||||
rating = str(item.get("CommunityRating"))
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
if item.get("Type") == "Episode" or item.get("Type") == "Season":
|
||||
series_id = item.get("SeriesId")
|
||||
|
||||
poster = downloadUtils.getArtwork(item, "SeriesPrimary")
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
medium_fanart = downloadUtils.getArtwork(item, "Backdrop3")
|
||||
banner = downloadUtils.getArtwork(item, "Banner")
|
||||
if item.get("SeriesThumbImageTag") != None:
|
||||
seriesthumbnail = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
seriesthumbnail = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".EpisodeTitle = " + title, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".ShowTitle = " + seriesName, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".EpisodeNo = " + tempEpisodeNumber, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".SeasonNo = " + tempSeasonNumber, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Rating = " + rating, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart) = " + fanart, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo) = " + logo, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.banner) = " + banner, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.poster) = " + poster, level=2)
|
||||
self.logMsg("LatestUnplayedEpisodeMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".EpisodeTitle", title)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".ShowTitle", seriesName)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".EpisodeNo", tempEpisodeNumber)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".SeasonNo", tempSeasonNumber)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".SeriesThumb", seriesthumbnail)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Rating", rating)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.fanart)", fanart)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.medium_fanart)", medium_fanart)
|
||||
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.clearlogo)", logo)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.banner)", banner)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Art(tvshow.poster)", poster)
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3." + str(item_count) + ".Plot", plot)
|
||||
|
||||
WINDOW.setProperty("LatestUnplayedEpisodeMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
#Updating Recent MusicList
|
||||
self.logMsg("Updating Recent MusicList")
|
||||
|
||||
recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=10&Recursive=true&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed,IsFolder&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=MusicAlbum&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Recent MusicList Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
artist = "Missing Artist"
|
||||
if(item.get("AlbumArtist") != None):
|
||||
artist = item.get("AlbumArtist").encode('utf-8')
|
||||
|
||||
year = "0000"
|
||||
if(item.get("ProductionYear") != None):
|
||||
year = str(item.get("ProductionYear"))
|
||||
plot = "Missing Plot"
|
||||
if(item.get("Overview") != None):
|
||||
plot = item.get("Overview").encode('utf-8')
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
if item.get("Type") == "MusicAlbum":
|
||||
parentId = item.get("ParentLogoItemId")
|
||||
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
banner = downloadUtils.getArtwork(item, "Banner")
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Title = " + title, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Artist = " + artist, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Year = " + year, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Art(banner) = " + banner, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2)
|
||||
self.logMsg("LatestAlbumMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
|
||||
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Title", title)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Artist", artist)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Year", year)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Rating", rating)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Art(fanart)", fanart)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Art(clearlogo)", logo)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Art(banner)", banner)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Art(poster)", thumbnail)
|
||||
WINDOW.setProperty("LatestAlbumMB3." + str(item_count) + ".Plot", plot)
|
||||
|
||||
WINDOW.setProperty("LatestAlbumMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
#Updating Recent Photo
|
||||
self.logMsg("Updating Recent Photo")
|
||||
|
||||
recentUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Users/" + userid + "/Items?Limit=10&Recursive=true&SortBy=DateCreated&Fields=Path,Genres,MediaStreams,Overview&SortOrder=Descending&Filters=IsUnplayed&IsVirtualUnaired=false&IsMissing=False&IncludeItemTypes=Photo&format=json"
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(recentUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Recent Photo Json Data : " + str(result), level=2)
|
||||
|
||||
result = result.get("Items")
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
|
||||
plot = "Missing Plot"
|
||||
if(item.get("Overview") != None):
|
||||
plot = item.get("Overview").encode('utf-8')
|
||||
|
||||
item_id = item.get("Id")
|
||||
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
banner = downloadUtils.getArtwork(item, "Banner")
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("LatestPhotoMB3." + str(item_count) + ".Title = " + title, level=2)
|
||||
self.logMsg("LatestPhotoMB3." + str(item_count) + ".Thumb = " + thumbnail, level=2)
|
||||
self.logMsg("LatestPhotoMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("LatestPhotoMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2)
|
||||
self.logMsg("LatestPhotoMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2)
|
||||
self.logMsg("LatestPhotoMB3." + str(item_count) + ".Art(banner) = " + banner, level=2)
|
||||
self.logMsg("LatestPhotoMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2)
|
||||
self.logMsg("LatestPhotoMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
|
||||
|
||||
WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Title", title)
|
||||
WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Thumb", thumbnail)
|
||||
WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Art(fanart)", fanart)
|
||||
WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Art(clearlogo)", logo)
|
||||
WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Art(banner)", banner)
|
||||
WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Art(poster)", thumbnail)
|
||||
WINDOW.setProperty("LatestPhotoMB3." + str(item_count) + ".Plot", plot)
|
||||
|
||||
WINDOW.setProperty("LatestPhotoMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
334
resources/lib/SearchDialog.py
Normal file
334
resources/lib/SearchDialog.py
Normal file
@ -0,0 +1,334 @@
|
||||
import sys
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import json as json
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
import threading
|
||||
|
||||
_MODE_ITEM_DETAILS=17
|
||||
|
||||
class SearchDialog(xbmcgui.WindowXMLDialog):
|
||||
|
||||
searchThread = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
|
||||
|
||||
def onInit(self):
|
||||
self.action_exitkeys_id = [10, 13]
|
||||
|
||||
self.searchThread = BackgroundSearchThread()
|
||||
self.searchThread.setDialog(self)
|
||||
self.searchThread.start()
|
||||
|
||||
|
||||
def onFocus(self, controlId):
|
||||
pass
|
||||
|
||||
def onAction(self, action):
|
||||
#xbmc.log("onAction : " + str(action.getId()) + " " + str(action.getButtonCode()) + " " + str(action))
|
||||
|
||||
ACTION_PREVIOUS_MENU = 10
|
||||
ACTION_SELECT_ITEM = 7
|
||||
ACTION_PARENT_DIR = 9
|
||||
|
||||
if action == ACTION_PREVIOUS_MENU or action.getId() == 92:
|
||||
searchTerm = self.getControl(3010).getLabel()
|
||||
if(len(searchTerm) == 0):
|
||||
self.close()
|
||||
else:
|
||||
searchTerm = searchTerm[:-1]
|
||||
self.getControl(3010).setLabel(searchTerm)
|
||||
self.searchThread.setSearch(searchTerm)
|
||||
|
||||
#self.getControl(3010).setLabel(str(action.getButtonCode()))
|
||||
|
||||
|
||||
def closeDialog(self):
|
||||
thread.stopRunning()
|
||||
self.close()
|
||||
|
||||
def onClick(self, controlID):
|
||||
|
||||
if(controlID == 3020):
|
||||
self.addCharacter("A")
|
||||
elif(controlID == 3021):
|
||||
self.addCharacter("B")
|
||||
elif(controlID == 3022):
|
||||
self.addCharacter("C")
|
||||
elif(controlID == 3023):
|
||||
self.addCharacter("D")
|
||||
elif(controlID == 3024):
|
||||
self.addCharacter("E")
|
||||
elif(controlID == 3025):
|
||||
self.addCharacter("F")
|
||||
elif(controlID == 3026):
|
||||
self.addCharacter("G")
|
||||
elif(controlID == 3027):
|
||||
self.addCharacter("H")
|
||||
elif(controlID == 3028):
|
||||
self.addCharacter("I")
|
||||
elif(controlID == 3029):
|
||||
self.addCharacter("J")
|
||||
elif(controlID == 3030):
|
||||
self.addCharacter("K")
|
||||
elif(controlID == 3031):
|
||||
self.addCharacter("L")
|
||||
elif(controlID == 3032):
|
||||
self.addCharacter("M")
|
||||
elif(controlID == 3033):
|
||||
self.addCharacter("N")
|
||||
elif(controlID == 3034):
|
||||
self.addCharacter("O")
|
||||
elif(controlID == 3035):
|
||||
self.addCharacter("P")
|
||||
elif(controlID == 3036):
|
||||
self.addCharacter("Q")
|
||||
elif(controlID == 3037):
|
||||
self.addCharacter("R")
|
||||
elif(controlID == 3038):
|
||||
self.addCharacter("S")
|
||||
elif(controlID == 3039):
|
||||
self.addCharacter("T")
|
||||
elif(controlID == 3040):
|
||||
self.addCharacter("U")
|
||||
elif(controlID == 3041):
|
||||
self.addCharacter("V")
|
||||
elif(controlID == 3042):
|
||||
self.addCharacter("W")
|
||||
elif(controlID == 3043):
|
||||
self.addCharacter("X")
|
||||
elif(controlID == 3044):
|
||||
self.addCharacter("Y")
|
||||
elif(controlID == 3045):
|
||||
self.addCharacter("Z")
|
||||
elif(controlID == 3046):
|
||||
self.addCharacter("0")
|
||||
elif(controlID == 3047):
|
||||
self.addCharacter("1")
|
||||
elif(controlID == 3048):
|
||||
self.addCharacter("2")
|
||||
elif(controlID == 3049):
|
||||
self.addCharacter("3")
|
||||
elif(controlID == 3050):
|
||||
self.addCharacter("4")
|
||||
elif(controlID == 3051):
|
||||
self.addCharacter("5")
|
||||
elif(controlID == 3052):
|
||||
self.addCharacter("6")
|
||||
elif(controlID == 3053):
|
||||
self.addCharacter("7")
|
||||
elif(controlID == 3054):
|
||||
self.addCharacter("8")
|
||||
elif(controlID == 3055):
|
||||
self.addCharacter("9")
|
||||
elif(controlID == 3056):
|
||||
searchTerm = self.getControl(3010).getLabel()
|
||||
searchTerm = searchTerm[:-1]
|
||||
self.getControl(3010).setLabel(searchTerm)
|
||||
self.searchThread.setSearch(searchTerm)
|
||||
elif(controlID == 3057):
|
||||
self.addCharacter(" ")
|
||||
elif(controlID == 3058):
|
||||
self.getControl(3010).setLabel("")
|
||||
self.searchThread.setSearch("")
|
||||
|
||||
elif(controlID == 3110):
|
||||
|
||||
#xbmc.executebuiltin("Dialog.Close(all,true)")
|
||||
itemList = self.getControl(3110)
|
||||
item = itemList.getSelectedItem()
|
||||
action = item.getProperty("ActionUrl")
|
||||
xbmc.executebuiltin("RunPlugin(" + action + ")")
|
||||
elif(controlID == 3111):
|
||||
|
||||
#xbmc.executebuiltin("Dialog.Close(all,true)")
|
||||
itemList = self.getControl(3111)
|
||||
item = itemList.getSelectedItem()
|
||||
action = item.getProperty("ActionUrl")
|
||||
xbmc.executebuiltin("RunPlugin(" + action + ")")
|
||||
elif(controlID == 3112):
|
||||
|
||||
#xbmc.executebuiltin("Dialog.Close(all,true)")
|
||||
itemList = self.getControl(3112)
|
||||
item = itemList.getSelectedItem()
|
||||
action = item.getProperty("ActionUrl")
|
||||
xbmc.executebuiltin("RunPlugin(" + action + ")")
|
||||
|
||||
pass
|
||||
|
||||
def addCharacter(self, char):
|
||||
searchTerm = self.getControl(3010).getLabel()
|
||||
searchTerm = searchTerm + char
|
||||
self.getControl(3010).setLabel(searchTerm)
|
||||
self.searchThread.setSearch(searchTerm)
|
||||
|
||||
class BackgroundSearchThread(threading.Thread):
|
||||
|
||||
active = True
|
||||
searchDialog = None
|
||||
searchString = ""
|
||||
|
||||
def __init__(self, *args):
|
||||
xbmc.log("BackgroundSearchThread Init")
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def setSearch(self, searchFor):
|
||||
self.searchString = searchFor
|
||||
|
||||
def stopRunning(self):
|
||||
self.active = False
|
||||
|
||||
def setDialog(self, searchDialog):
|
||||
self.searchDialog = searchDialog
|
||||
|
||||
def run(self):
|
||||
xbmc.log("BackgroundSearchThread Started")
|
||||
|
||||
lastSearchString = ""
|
||||
|
||||
while(xbmc.abortRequested == False and self.active == True):
|
||||
currentSearch = self.searchString
|
||||
if(currentSearch != lastSearchString):
|
||||
lastSearchString = currentSearch
|
||||
self.doSearch(currentSearch)
|
||||
|
||||
xbmc.sleep(2000)
|
||||
|
||||
xbmc.log("BackgroundSearchThread Exited")
|
||||
|
||||
def doSearch(self, searchTerm):
|
||||
|
||||
movieResultsList = self.searchDialog.getControl(3110)
|
||||
while(movieResultsList.size() > 0):
|
||||
movieResultsList.removeItem(0)
|
||||
#movieResultsList.reset()
|
||||
|
||||
|
||||
seriesResultsList = self.searchDialog.getControl(3111)
|
||||
while(seriesResultsList.size() > 0):
|
||||
seriesResultsList.removeItem(0)
|
||||
#seriesResultsList.reset()
|
||||
|
||||
episodeResultsList = self.searchDialog.getControl(3112)
|
||||
while(episodeResultsList.size() > 0):
|
||||
episodeResultsList.removeItem(0)
|
||||
#episodeResultsList.reset()
|
||||
|
||||
if(len(searchTerm) == 0):
|
||||
return
|
||||
|
||||
__settings__ = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
port = __settings__.getSetting('port')
|
||||
host = __settings__.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
#
|
||||
# Process movies
|
||||
#
|
||||
search = urllib.quote(searchTerm)
|
||||
url = "http://" + server + "/mediabrowser/Search/Hints?SearchTerm=" + search + "&Limit=10&IncludeItemTypes=Movie&format=json"
|
||||
jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1)
|
||||
result = json.loads(jsonData)
|
||||
|
||||
items = result.get("SearchHints")
|
||||
|
||||
if(items == None or len(items) == 0):
|
||||
item = []
|
||||
|
||||
for item in items:
|
||||
xbmc.log(str(item))
|
||||
|
||||
item_id = item.get("ItemId")
|
||||
item_name = item.get("Name")
|
||||
item_type = item.get("Type")
|
||||
|
||||
typeLabel = "Movie"
|
||||
|
||||
thumbPath = downloadUtils.imageUrl(item_id, "Primary", 0, 200, 200)
|
||||
xbmc.log(thumbPath)
|
||||
|
||||
listItem = xbmcgui.ListItem(label=item_name, label2=typeLabel, iconImage=thumbPath, thumbnailImage=thumbPath)
|
||||
|
||||
actionUrl = "plugin://plugin.video.xbmb3c?id=" + item_id + "&mode=" + str(_MODE_ITEM_DETAILS)
|
||||
listItem.setProperty("ActionUrl", actionUrl)
|
||||
|
||||
movieResultsList.addItem(listItem)
|
||||
|
||||
#
|
||||
# Process series
|
||||
#
|
||||
search = urllib.quote(searchTerm)
|
||||
url = "http://" + server + "/mediabrowser/Search/Hints?SearchTerm=" + search + "&Limit=10&IncludeItemTypes=Series&format=json"
|
||||
jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
items = result.get("SearchHints")
|
||||
|
||||
if(items == None or len(items) == 0):
|
||||
item = []
|
||||
|
||||
for item in items:
|
||||
xbmc.log(str(item))
|
||||
|
||||
item_id = item.get("ItemId")
|
||||
item_name = item.get("Name")
|
||||
item_type = item.get("Type")
|
||||
|
||||
typeLabel = ""
|
||||
image_id = ""
|
||||
|
||||
image_id = item.get("ItemId")
|
||||
typeLabel = "Series"
|
||||
|
||||
thumbPath = downloadUtils.imageUrl(image_id, "Primary", 0, 200, 200)
|
||||
xbmc.log(thumbPath)
|
||||
|
||||
listItem = xbmcgui.ListItem(label=item_name, label2=typeLabel, iconImage=thumbPath, thumbnailImage=thumbPath)
|
||||
|
||||
actionUrl = "plugin://plugin.video.xbmb3c?id=" + item_id + "&mode=" + str(_MODE_ITEM_DETAILS)
|
||||
listItem.setProperty("ActionUrl", actionUrl)
|
||||
|
||||
seriesResultsList.addItem(listItem)
|
||||
|
||||
#
|
||||
# Process episodes
|
||||
#
|
||||
search = urllib.quote(searchTerm)
|
||||
url = "http://" + server + "/mediabrowser/Search/Hints?SearchTerm=" + search + "&Limit=10&IncludeItemTypes=Episode&format=json"
|
||||
jsonData = downloadUtils.downloadUrl(url, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
items = result.get("SearchHints")
|
||||
|
||||
if(items == None or len(items) == 0):
|
||||
item = []
|
||||
|
||||
for item in items:
|
||||
xbmc.log(str(item))
|
||||
|
||||
item_id = item.get("ItemId")
|
||||
item_name = item.get("Name")
|
||||
item_type = item.get("Type")
|
||||
|
||||
image_id = item.get("ThumbImageItemId")
|
||||
season = item.get("ParentIndexNumber")
|
||||
eppNum = item.get("IndexNumber")
|
||||
typeLabel = "S" + str(season).zfill(2) + "E" + str(eppNum).zfill(2)
|
||||
|
||||
thumbPath = downloadUtils.imageUrl(image_id, "Primary", 0, 200, 200)
|
||||
|
||||
xbmc.log(thumbPath)
|
||||
|
||||
listItem = xbmcgui.ListItem(label=item_name, label2=typeLabel, iconImage=thumbPath, thumbnailImage=thumbPath)
|
||||
|
||||
actionUrl = "plugin://plugin.video.xbmb3c?id=" + item_id + "&mode=" + str(_MODE_ITEM_DETAILS)
|
||||
listItem.setProperty("ActionUrl", actionUrl)
|
||||
|
||||
episodeResultsList.addItem(listItem)
|
||||
|
||||
|
161
resources/lib/SuggestedItems.py
Normal file
161
resources/lib/SuggestedItems.py
Normal file
@ -0,0 +1,161 @@
|
||||
#################################################################################################
|
||||
# Suggested Updater
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
class SuggestedUpdaterThread(threading.Thread):
|
||||
|
||||
logLevel = 0
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C SuggestedUpdaterThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C SuggestedUpdaterThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
self.logMsg("Started")
|
||||
|
||||
self.updateSuggested()
|
||||
lastRun = datetime.today()
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
td = datetime.today() - lastRun
|
||||
secTotal = td.seconds
|
||||
|
||||
if(secTotal > 300):
|
||||
self.updateSuggested()
|
||||
lastRun = datetime.today()
|
||||
|
||||
xbmc.sleep(3000)
|
||||
|
||||
self.logMsg("Exited")
|
||||
|
||||
def updateSuggested(self):
|
||||
self.logMsg("updateSuggested Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
userid = downloadUtils.getUserId()
|
||||
self.logMsg("updateSuggested UserID : " + userid)
|
||||
|
||||
self.logMsg("Updating Suggested List")
|
||||
|
||||
suggestedUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Movies/Recommendations?UserId=" + userid + "&categoryLimit=1&ItemLimit=6&format=json"
|
||||
jsonData = downloadUtils.downloadUrl(suggestedUrl, suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
self.logMsg("Suggested Movie Json Data : " + str(result), level=2)
|
||||
basemovie = "Missing Base Title"
|
||||
|
||||
if(result == None or len(result) == 0):
|
||||
return
|
||||
|
||||
if (result[0].get("BaselineItemName") != None):
|
||||
basemovie = result[0].get("BaselineItemName").encode('utf-8')
|
||||
|
||||
result = result[0].get("Items")
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
if(result == None):
|
||||
result = []
|
||||
|
||||
item_count = 1
|
||||
for item in result:
|
||||
title = "Missing Title"
|
||||
if(item.get("Name") != None):
|
||||
title = item.get("Name").encode('utf-8')
|
||||
|
||||
rating = item.get("CommunityRating")
|
||||
criticrating = item.get("CriticRating")
|
||||
officialrating = item.get("OfficialRating")
|
||||
criticratingsummary = ""
|
||||
if(item.get("CriticRatingSummary") != None):
|
||||
criticratingsummary = item.get("CriticRatingSummary").encode('utf-8')
|
||||
plot = item.get("Overview")
|
||||
if plot == None:
|
||||
plot=''
|
||||
plot=plot.encode('utf-8')
|
||||
year = item.get("ProductionYear")
|
||||
if(item.get("RunTimeTicks") != None):
|
||||
runtime = str(int(item.get("RunTimeTicks"))/(10000000*60))
|
||||
else:
|
||||
runtime = "0"
|
||||
|
||||
item_id = item.get("Id")
|
||||
thumbnail = downloadUtils.getArtwork(item, "Primary")
|
||||
logo = downloadUtils.getArtwork(item, "Logo")
|
||||
fanart = downloadUtils.getArtwork(item, "Backdrop")
|
||||
medium_fanart = downloadUtils.getArtwork(item, "Backdrop3")
|
||||
if item.get("ImageTags").get("Thumb") != None:
|
||||
realthumbnail = downloadUtils.getArtwork(item, "Thumb3")
|
||||
else:
|
||||
realthumbnail = fanart
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + item_id
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Title = " + title, level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Thumb = " + realthumbnail, level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Path = " + playUrl, level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Art(fanart) = " + fanart, level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Art(clearlogo) = " + logo, level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Art(poster) = " + thumbnail, level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Rating = " + str(rating), level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".CriticRating = " + str(criticrating), level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".CriticRatingSummary = " + criticratingsummary, level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Plot = " + plot, level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Year = " + str(year), level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".Runtime = " + str(runtime), level=2)
|
||||
self.logMsg("SuggestedMovieMB3." + str(item_count) + ".SuggestedMovieTitle = " + basemovie, level=2)
|
||||
|
||||
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Title", title)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Thumb", realthumbnail)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Path", playUrl)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Art(fanart)", fanart)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Art(medium_fanart)", medium_fanart)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Art(clearlogo)", logo)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Art(poster)", thumbnail)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Rating", str(rating))
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Mpaa", str(officialrating))
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".CriticRating", str(criticrating))
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".CriticRatingSummary", criticratingsummary)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Plot", plot)
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Year", str(year))
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".Runtime", str(runtime))
|
||||
WINDOW.setProperty("SuggestedMovieMB3." + str(item_count) + ".SuggestedMovieTitle", basemovie)
|
||||
|
||||
|
||||
WINDOW.setProperty("SuggestedMovieMB3.Enabled", "true")
|
||||
|
||||
item_count = item_count + 1
|
||||
|
||||
|
183
resources/lib/ThemeMusic.py
Normal file
183
resources/lib/ThemeMusic.py
Normal file
@ -0,0 +1,183 @@
|
||||
#################################################################################################
|
||||
# Start of ThemeMusic Thread
|
||||
# plays theme music when applicable
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
import urllib2
|
||||
import random
|
||||
|
||||
from Utils import PlayUtils
|
||||
from DownloadUtils import DownloadUtils
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
class ThemeMusicThread(threading.Thread):
|
||||
|
||||
playingTheme = False
|
||||
themeId = ''
|
||||
volume = ''
|
||||
themeMap = {}
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C ThemeMusicThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C ThemeMusicThread -> " + msg)
|
||||
|
||||
def run(self):
|
||||
self.logMsg("Started")
|
||||
self.updateThemeMusic()
|
||||
lastRun = datetime.today()
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
themeRefresh = 2
|
||||
|
||||
while (xbmc.abortRequested == False):
|
||||
td = datetime.today() - lastRun
|
||||
secTotal = td.seconds
|
||||
|
||||
if (secTotal > themeRefresh):
|
||||
self.updateThemeMusic()
|
||||
lastRun = datetime.today()
|
||||
|
||||
xbmc.sleep(2000)
|
||||
|
||||
self.logMsg("Exited")
|
||||
|
||||
|
||||
def updateThemeMusic(self):
|
||||
self.logMsg("updateThemeMusic Called")
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
|
||||
newid = xbmc.getInfoLabel('ListItem.Property(ItemGUID)')
|
||||
if newid != self.themeId:
|
||||
if self.isPlayingZone() and self.playingTheme == True:
|
||||
if xbmc.Player().isPlayingAudio():
|
||||
self.stop()
|
||||
xbmc.sleep(1500)
|
||||
id = xbmc.getInfoLabel('ListItem.Property(ItemGUID)')
|
||||
if id != newid:
|
||||
return
|
||||
self.logMsg("updateThemeMusic itemGUID : " + id)
|
||||
if self.isPlayingZone() and self.isChangeTheme():
|
||||
self.themeId = id
|
||||
themeUrl = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Items/" + id + "/ThemeSongs?format=json"
|
||||
self.logMsg("updateThemeMusic themeUrl : " + themeUrl)
|
||||
if themeUrl not in self.themeMap:
|
||||
jsonData = downloadUtils.downloadUrl(themeUrl, suppress=False, popup=1 )
|
||||
theme = json.loads(jsonData)
|
||||
|
||||
if(theme == None):
|
||||
theme = []
|
||||
self.logMsg("updateThemeMusic added theme to map : " + themeUrl)
|
||||
self.themeMap[themeUrl] = theme
|
||||
elif themeUrl in self.themeMap:
|
||||
theme = self.themeMap.get(themeUrl)
|
||||
self.logMsg("updateThemeMusic retrieved theme from map : " + themeUrl)
|
||||
|
||||
themeItems = theme.get("Items")
|
||||
if themeItems != []:
|
||||
themePlayUrl = PlayUtils().getPlayUrl(mb3Host + ":" + mb3Port,themeItems[0].get("Id"),themeItems[0])
|
||||
self.logMsg("updateThemeMusic themeMusicPath : " + str(themePlayUrl))
|
||||
self.playingTheme = True
|
||||
self.setVolume(60)
|
||||
xbmc.Player().play(themePlayUrl)
|
||||
|
||||
elif themeItems == [] and self.playingTheme == True:
|
||||
self.stop(True)
|
||||
|
||||
if not self.isPlayingZone() and self.playingTheme == True:
|
||||
# stop
|
||||
if xbmc.Player().isPlayingAudio():
|
||||
self.stop()
|
||||
|
||||
|
||||
def stop(self, forceStop = False):
|
||||
# Only stop if playing audio
|
||||
if xbmc.Player().isPlayingAudio() or forceStop == True:
|
||||
self.playingTheme = False
|
||||
cur_vol = self.getVolume()
|
||||
|
||||
# Calculate how fast to fade the theme, this determines
|
||||
# the number of step to drop the volume in
|
||||
numSteps = 15
|
||||
vol_step = cur_vol / numSteps
|
||||
# do not mute completely else the mute icon shows up
|
||||
for step in range (0,(numSteps-1)):
|
||||
vol = cur_vol - vol_step
|
||||
self.setVolume(vol)
|
||||
cur_vol = vol
|
||||
xbmc.sleep(200)
|
||||
xbmc.Player().stop()
|
||||
self.setVolume(self.volume)
|
||||
|
||||
# Works out if the currently displayed area on the screen is something
|
||||
# that is deemed a zone where themes should be played
|
||||
def isPlayingZone(self):
|
||||
|
||||
if "plugin://plugin.video.xbmb3c" in xbmc.getInfoLabel( "ListItem.Path" ):
|
||||
return True
|
||||
|
||||
# Any other area is deemed to be a non play area
|
||||
return False
|
||||
|
||||
# Works out if we should change/start a theme
|
||||
def isChangeTheme(self):
|
||||
id = xbmc.getInfoLabel('ListItem.Property(ItemGUID)')
|
||||
if id != "":
|
||||
if self.volume == '':
|
||||
self.volume = self.getVolume()
|
||||
# we have something to start with
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
if addonSettings.getSetting('useThemeMusic') == "true":
|
||||
# cool theme music is on continue
|
||||
if id == self.themeId:
|
||||
# same as before now do we need to restart
|
||||
if addonSettings.getSetting('loopThemeMusic') == "true" and xbmc.Player().isPlayingAudio() == False:
|
||||
return True
|
||||
if id != self.themeId:
|
||||
# new id return true
|
||||
return True
|
||||
|
||||
# still here return False
|
||||
return False
|
||||
|
||||
# This will return the volume in a range of 0-100
|
||||
def getVolume(self):
|
||||
result = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": { "properties": [ "volume" ] }, "id": 1}')
|
||||
|
||||
json_query = json.loads(result)
|
||||
if "result" in json_query and json_query['result'].has_key('volume'):
|
||||
# Get the volume value
|
||||
volume = json_query['result']['volume']
|
||||
|
||||
return volume
|
||||
|
||||
# Sets the volume in the range 0-100
|
||||
def setVolume(self, newvolume):
|
||||
# Can't use the RPC version as that will display the volume dialog
|
||||
# '{"jsonrpc": "2.0", "method": "Application.SetVolume", "params": { "volume": %d }, "id": 1}'
|
||||
xbmc.executebuiltin('XBMC.SetVolume(%d)' % newvolume, True)
|
||||
|
||||
|
167
resources/lib/Utils.py
Normal file
167
resources/lib/Utils.py
Normal file
@ -0,0 +1,167 @@
|
||||
#################################################################################################
|
||||
# utils class
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from DownloadUtils import DownloadUtils
|
||||
import urllib
|
||||
import sys
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
###########################################################################
|
||||
class PlayUtils():
|
||||
|
||||
def getPlayUrl(self, server, id, result):
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
# if the path is local and depending on the video quality play we can direct play it do so-
|
||||
xbmc.log("XBMB3C getPlayUrl")
|
||||
if self.isDirectPlay(result) == True:
|
||||
xbmc.log("XBMB3C getPlayUrl -> Direct Play")
|
||||
playurl = result.get("Path")
|
||||
if playurl != None:
|
||||
#We have a path to play so play it
|
||||
USER_AGENT = 'QuickTime/7.7.4'
|
||||
|
||||
# If the file it is not a media stub
|
||||
if (result.get("IsPlaceHolder") != True):
|
||||
if (result.get("VideoType") == "Dvd"):
|
||||
playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO"
|
||||
elif (result.get("VideoType") == "BluRay"):
|
||||
playurl = playurl + "/BDMV/index.bdmv"
|
||||
if addonSettings.getSetting('smbusername') == '':
|
||||
playurl = playurl.replace("\\\\", "smb://")
|
||||
else:
|
||||
playurl = playurl.replace("\\\\", "smb://" + addonSettings.getSetting('smbusername') + ':' + addonSettings.getSetting('smbpassword') + '@')
|
||||
playurl = playurl.replace("\\", "/")
|
||||
|
||||
if ("apple.com" in playurl):
|
||||
playurl += '?|User-Agent=%s' % USER_AGENT
|
||||
if addonSettings.getSetting('playFromStream') == "true":
|
||||
playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/stream?static=true'
|
||||
mediaSources = result.get("MediaSources")
|
||||
if(mediaSources != None):
|
||||
if mediaSources[0].get('DefaultAudioStreamIndex') != None:
|
||||
playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex'))
|
||||
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
|
||||
playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultAudioStreamIndex'))
|
||||
|
||||
|
||||
# elif self.isNetworkQualitySufficient(result) == True:
|
||||
# xbmc.log("XBMB3C getPlayUrl -> Stream")
|
||||
#No direct path but sufficient network so static stream
|
||||
# if result.get("Type") == "Audio":
|
||||
# playurl = 'http://' + server + '/mediabrowser/Audio/' + id + '/stream?static=true&mediaSourceId=' + id
|
||||
#else:
|
||||
# playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/stream?static=true&mediaSourceId=' + id
|
||||
else:
|
||||
#No path or has a path but not sufficient network so transcode
|
||||
xbmc.log("XBMB3C getPlayUrl -> Transcode")
|
||||
if result.get("Type") == "Audio":
|
||||
playurl = 'http://' + server + '/mediabrowser/Audio/' + id + '/stream.mp3'
|
||||
else:
|
||||
txt_mac = downloadUtils.getMachineId()
|
||||
playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/master.m3u8?mediaSourceId=' + id
|
||||
playurl = playurl + '&videoCodec=h264'
|
||||
playurl = playurl + '&AudioCodec=aac,ac3'
|
||||
playurl = playurl + '&deviceId=' + txt_mac
|
||||
playurl = playurl + '&VideoBitrate=' + str(int(self.getVideoBitRate()) * 1000)
|
||||
mediaSources = result.get("MediaSources")
|
||||
if(mediaSources != None):
|
||||
if mediaSources[0].get('DefaultAudioStreamIndex') != None:
|
||||
playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex'))
|
||||
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
|
||||
playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultAudioStreamIndex'))
|
||||
return playurl.encode('utf-8')
|
||||
|
||||
# Works out if we are direct playing or not
|
||||
def isDirectPlay(self, result):
|
||||
if result.get("LocationType") == "FileSystem" and self.isNetworkQualitySufficient(result) == True and self.isLocalPath(result) == False:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# Works out if the network quality can play directly or if transcoding is needed
|
||||
def isNetworkQualitySufficient(self, result):
|
||||
settingsVideoBitRate = self.getVideoBitRate()
|
||||
settingsVideoBitRate = int(settingsVideoBitRate) * 1000
|
||||
mediaSources = result.get("MediaSources")
|
||||
if(mediaSources != None):
|
||||
if mediaSources[0].get('Bitrate') != None:
|
||||
if settingsVideoBitRate < int(mediaSources[0].get('Bitrate')):
|
||||
xbmc.log("XBMB3C isNetworkQualitySufficient -> FALSE bit rate - settingsVideoBitRate: " + str(settingsVideoBitRate) + " mediasource bitrate: " + str(mediaSources[0].get('Bitrate')))
|
||||
return False
|
||||
else:
|
||||
xbmc.log("XBMB3C isNetworkQualitySufficient -> TRUE bit rate")
|
||||
return True
|
||||
|
||||
# Any thing else is ok
|
||||
xbmc.log("XBMB3C isNetworkQualitySufficient -> TRUE default")
|
||||
return True
|
||||
|
||||
|
||||
# get the addon video quality
|
||||
def getVideoBitRate(self):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
videoQuality = addonSettings.getSetting('videoBitRate')
|
||||
if (videoQuality == "0"):
|
||||
return '664'
|
||||
elif (videoQuality == "1"):
|
||||
return '996'
|
||||
elif (videoQuality == "2"):
|
||||
return '1320'
|
||||
elif (videoQuality == "3"):
|
||||
return '2000'
|
||||
elif (videoQuality == "4"):
|
||||
return '3200'
|
||||
elif (videoQuality == "5"):
|
||||
return '4700'
|
||||
elif (videoQuality == "6"):
|
||||
return '6200'
|
||||
elif (videoQuality == "7"):
|
||||
return '7700'
|
||||
elif (videoQuality == "8"):
|
||||
return '9200'
|
||||
elif (videoQuality == "9"):
|
||||
return '10700'
|
||||
elif (videoQuality == "10"):
|
||||
return '12200'
|
||||
elif (videoQuality == "11"):
|
||||
return '13700'
|
||||
elif (videoQuality == "12"):
|
||||
return '15200'
|
||||
elif (videoQuality == "13"):
|
||||
return '16700'
|
||||
elif (videoQuality == "14"):
|
||||
return '18200'
|
||||
elif (videoQuality == "15"):
|
||||
return '20000'
|
||||
elif (videoQuality == "16"):
|
||||
return '40000'
|
||||
elif (videoQuality == "17"):
|
||||
return '100000'
|
||||
elif (videoQuality == "18"):
|
||||
return '1000000'
|
||||
|
||||
# Works out if the network quality can play directly or if transcoding is needed
|
||||
def isLocalPath(self, result):
|
||||
playurl = result.get("Path")
|
||||
if playurl != None:
|
||||
#We have a path to play so play it
|
||||
if ":\\" in playurl:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# default to not local
|
||||
return False
|
||||
|
244
resources/lib/WebSocketClient.py
Normal file
244
resources/lib/WebSocketClient.py
Normal file
@ -0,0 +1,244 @@
|
||||
#################################################################################################
|
||||
# WebSocket Client thread
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
import urllib
|
||||
import socket
|
||||
import websocket
|
||||
from ClientInformation import ClientInformation
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
class WebSocketThread(threading.Thread):
|
||||
|
||||
logLevel = 0
|
||||
client = None
|
||||
keepRunning = True
|
||||
|
||||
def __init__(self, *args):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
level = addonSettings.getSetting('logLevel')
|
||||
self.logLevel = 0
|
||||
if(level != None):
|
||||
self.logLevel = int(level)
|
||||
|
||||
xbmc.log("XBMB3C WebSocketThread -> Log Level:" + str(self.logLevel))
|
||||
|
||||
threading.Thread.__init__(self, *args)
|
||||
|
||||
def logMsg(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
xbmc.log("XBMB3C WebSocketThread -> " + msg)
|
||||
|
||||
def playbackStarted(self, itemId):
|
||||
if(self.client != None):
|
||||
try:
|
||||
self.logMsg("Sending Playback Started")
|
||||
messageData = {}
|
||||
messageData["MessageType"] = "PlaybackStart"
|
||||
messageData["Data"] = itemId + "|true|audio,video"
|
||||
messageString = json.dumps(messageData)
|
||||
self.logMsg("Message Data : " + messageString)
|
||||
self.client.send(messageString)
|
||||
except Exception, e:
|
||||
self.logMsg("Exception : " + str(e), level=0)
|
||||
else:
|
||||
self.logMsg("Sending Playback Started NO Object ERROR")
|
||||
|
||||
def playbackStopped(self, itemId, ticks):
|
||||
if(self.client != None):
|
||||
try:
|
||||
self.logMsg("Sending Playback Stopped")
|
||||
messageData = {}
|
||||
messageData["MessageType"] = "PlaybackStopped"
|
||||
messageData["Data"] = itemId + "|" + str(ticks)
|
||||
messageString = json.dumps(messageData)
|
||||
self.client.send(messageString)
|
||||
except Exception, e:
|
||||
self.logMsg("Exception : " + str(e), level=0)
|
||||
else:
|
||||
self.logMsg("Sending Playback Stopped NO Object ERROR")
|
||||
|
||||
def sendProgressUpdate(self, itemId, ticks):
|
||||
if(self.client != None):
|
||||
try:
|
||||
self.logMsg("Sending Progress Update")
|
||||
messageData = {}
|
||||
messageData["MessageType"] = "PlaybackProgress"
|
||||
messageData["Data"] = itemId + "|" + str(ticks) + "|false|false"
|
||||
messageString = json.dumps(messageData)
|
||||
self.logMsg("Message Data : " + messageString)
|
||||
self.client.send(messageString)
|
||||
except Exception, e:
|
||||
self.logMsg("Exception : " + str(e), level=0)
|
||||
else:
|
||||
self.logMsg("Sending Progress Update NO Object ERROR")
|
||||
|
||||
def stopClient(self):
|
||||
# stopping the client is tricky, first set keep_running to false and then trigger one
|
||||
# more message by requesting one SessionsStart message, this causes the
|
||||
# client to receive the message and then exit
|
||||
if(self.client != None):
|
||||
self.logMsg("Stopping Client")
|
||||
self.keepRunning = False
|
||||
self.client.keep_running = False
|
||||
self.logMsg("Stopping Client : KeepRunning set to False")
|
||||
'''
|
||||
try:
|
||||
self.keepRunning = False
|
||||
self.client.keep_running = False
|
||||
self.logMsg("Stopping Client")
|
||||
self.logMsg("Calling Ping")
|
||||
self.client.sock.ping()
|
||||
|
||||
self.logMsg("Calling Socket Shutdown()")
|
||||
self.client.sock.sock.shutdown(socket.SHUT_RDWR)
|
||||
self.logMsg("Calling Socket Close()")
|
||||
self.client.sock.sock.close()
|
||||
self.logMsg("Stopping Client Done")
|
||||
self.logMsg("Calling Ping")
|
||||
self.client.sock.ping()
|
||||
|
||||
except Exception, e:
|
||||
self.logMsg("Exception : " + str(e), level=0)
|
||||
'''
|
||||
else:
|
||||
self.logMsg("Stopping Client NO Object ERROR")
|
||||
|
||||
def on_message(self, ws, message):
|
||||
self.logMsg("Message : " + str(message))
|
||||
result = json.loads(message)
|
||||
|
||||
messageType = result.get("MessageType")
|
||||
playCommand = result.get("PlayCommand")
|
||||
data = result.get("Data")
|
||||
|
||||
if(messageType != None and messageType == "Play" and data != None):
|
||||
itemIds = data.get("ItemIds")
|
||||
playCommand = data.get("PlayCommand")
|
||||
if(playCommand != None and playCommand == "PlayNow"):
|
||||
|
||||
startPositionTicks = data.get("StartPositionTicks")
|
||||
self.logMsg("Playing Media With ID : " + itemIds[0])
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
|
||||
url = mb3Host + ":" + mb3Port + ',;' + itemIds[0]
|
||||
if(startPositionTicks == None):
|
||||
url += ",;" + "-1"
|
||||
else:
|
||||
url += ",;" + str(startPositionTicks)
|
||||
|
||||
playUrl = "plugin://plugin.video.xbmb3c/?url=" + url + '&mode=' + str(_MODE_BASICPLAY)
|
||||
playUrl = playUrl.replace("\\\\","smb://")
|
||||
playUrl = playUrl.replace("\\","/")
|
||||
|
||||
xbmc.Player().play(playUrl)
|
||||
|
||||
elif(messageType != None and messageType == "Playstate"):
|
||||
command = data.get("Command")
|
||||
if(command != None and command == "Stop"):
|
||||
self.logMsg("Playback Stopped")
|
||||
xbmc.executebuiltin('xbmc.activatewindow(10000)')
|
||||
xbmc.Player().stop()
|
||||
|
||||
if(command != None and command == "Seek"):
|
||||
seekPositionTicks = data.get("SeekPositionTicks")
|
||||
self.logMsg("Playback Seek : " + str(seekPositionTicks))
|
||||
seekTime = (seekPositionTicks / 1000) / 10000
|
||||
xbmc.Player().seekTime(seekTime)
|
||||
|
||||
def on_error(self, ws, error):
|
||||
self.logMsg("Error : " + str(error))
|
||||
|
||||
def on_close(self, ws):
|
||||
self.logMsg("Closed")
|
||||
|
||||
def on_open(self, ws):
|
||||
try:
|
||||
clientInfo = ClientInformation()
|
||||
machineId = clientInfo.getMachineId()
|
||||
version = clientInfo.getVersion()
|
||||
messageData = {}
|
||||
messageData["MessageType"] = "Identity"
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
deviceName = addonSettings.getSetting('deviceName')
|
||||
deviceName = deviceName.replace("\"", "_")
|
||||
|
||||
messageData["Data"] = "XBMC|" + machineId + "|" + version + "|" + deviceName
|
||||
messageString = json.dumps(messageData)
|
||||
self.logMsg("Opened : " + str(messageString))
|
||||
ws.send(messageString)
|
||||
except Exception, e:
|
||||
self.logMsg("Exception : " + str(e), level=0)
|
||||
|
||||
def getWebSocketPort(self, host, port):
|
||||
|
||||
userUrl = "http://" + host + ":" + port + "/mediabrowser/System/Info?format=json"
|
||||
|
||||
try:
|
||||
requesthandle = urllib.urlopen(userUrl, proxies={})
|
||||
jsonData = requesthandle.read()
|
||||
requesthandle.close()
|
||||
except Exception, e:
|
||||
self.logMsg("WebSocketThread getWebSocketPort urlopen : " + str(e) + " (" + userUrl + ")", level=0)
|
||||
return -1
|
||||
|
||||
try:
|
||||
result = json.loads(jsonData)
|
||||
except Exception, e:
|
||||
self.logMsg("WebSocketThread getWebSocketPort jsonload : " + str(e) + " (" + jsonData + ")", level=2)
|
||||
return -1
|
||||
|
||||
wsPort = result.get("WebSocketPortNumber")
|
||||
if(wsPort != None):
|
||||
return wsPort
|
||||
else:
|
||||
return -1
|
||||
|
||||
def run(self):
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
|
||||
if(self.logLevel >= 1):
|
||||
websocket.enableTrace(True)
|
||||
|
||||
wsPort = self.getWebSocketPort(mb3Host, mb3Port);
|
||||
self.logMsg("WebSocketPortNumber = " + str(wsPort))
|
||||
if(wsPort == -1):
|
||||
self.logMsg("Could not retrieve WebSocket port, can not run WebScoket Client")
|
||||
return
|
||||
|
||||
# Make a call to /System/Info. WebSocketPortNumber is the port hosting the web socket.
|
||||
webSocketUrl = "ws://" + mb3Host + ":" + str(wsPort) + "/mediabrowser"
|
||||
self.logMsg("WebSocket URL : " + webSocketUrl)
|
||||
self.client = websocket.WebSocketApp(webSocketUrl,
|
||||
on_message = self.on_message,
|
||||
on_error = self.on_error,
|
||||
on_close = self.on_close)
|
||||
|
||||
self.client.on_open = self.on_open
|
||||
|
||||
while(self.keepRunning):
|
||||
self.logMsg("Client Starting")
|
||||
self.client.run_forever()
|
||||
if(self.keepRunning):
|
||||
self.logMsg("Client Needs To Restart")
|
||||
xbmc.sleep(10000)
|
||||
|
||||
self.logMsg("Thread Exited")
|
||||
|
||||
|
||||
|
||||
|
1
resources/lib/__init__.py
Normal file
1
resources/lib/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Dummy file to make this directory a package.
|
902
resources/lib/websocket.py
Normal file
902
resources/lib/websocket.py
Normal file
@ -0,0 +1,902 @@
|
||||
"""
|
||||
websocket - WebSocket client library for Python
|
||||
|
||||
Copyright (C) 2010 Hiroki Ohtani(liris)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import socket
|
||||
|
||||
try:
|
||||
import ssl
|
||||
from ssl import SSLError
|
||||
HAVE_SSL = True
|
||||
except ImportError:
|
||||
# dummy class of SSLError for ssl none-support environment.
|
||||
class SSLError(Exception):
|
||||
pass
|
||||
|
||||
HAVE_SSL = False
|
||||
|
||||
from urlparse import urlparse
|
||||
import os
|
||||
import array
|
||||
import struct
|
||||
import uuid
|
||||
import hashlib
|
||||
import base64
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
"""
|
||||
websocket python client.
|
||||
=========================
|
||||
|
||||
This version support only hybi-13.
|
||||
Please see http://tools.ietf.org/html/rfc6455 for protocol.
|
||||
"""
|
||||
|
||||
|
||||
# websocket supported version.
|
||||
VERSION = 13
|
||||
|
||||
# closing frame status codes.
|
||||
STATUS_NORMAL = 1000
|
||||
STATUS_GOING_AWAY = 1001
|
||||
STATUS_PROTOCOL_ERROR = 1002
|
||||
STATUS_UNSUPPORTED_DATA_TYPE = 1003
|
||||
STATUS_STATUS_NOT_AVAILABLE = 1005
|
||||
STATUS_ABNORMAL_CLOSED = 1006
|
||||
STATUS_INVALID_PAYLOAD = 1007
|
||||
STATUS_POLICY_VIOLATION = 1008
|
||||
STATUS_MESSAGE_TOO_BIG = 1009
|
||||
STATUS_INVALID_EXTENSION = 1010
|
||||
STATUS_UNEXPECTED_CONDITION = 1011
|
||||
STATUS_TLS_HANDSHAKE_ERROR = 1015
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class WebSocketException(Exception):
|
||||
"""
|
||||
websocket exeception class.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class WebSocketConnectionClosedException(WebSocketException):
|
||||
"""
|
||||
If remote host closed the connection or some network error happened,
|
||||
this exception will be raised.
|
||||
"""
|
||||
pass
|
||||
|
||||
class WebSocketTimeoutException(WebSocketException):
|
||||
"""
|
||||
WebSocketTimeoutException will be raised at socket timeout during read/write data.
|
||||
"""
|
||||
pass
|
||||
|
||||
default_timeout = None
|
||||
traceEnabled = False
|
||||
|
||||
|
||||
def enableTrace(tracable):
|
||||
"""
|
||||
turn on/off the tracability.
|
||||
|
||||
tracable: boolean value. if set True, tracability is enabled.
|
||||
"""
|
||||
global traceEnabled
|
||||
traceEnabled = tracable
|
||||
if tracable:
|
||||
if not logger.handlers:
|
||||
logger.addHandler(logging.StreamHandler())
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def setdefaulttimeout(timeout):
|
||||
"""
|
||||
Set the global timeout setting to connect.
|
||||
|
||||
timeout: default socket timeout time. This value is second.
|
||||
"""
|
||||
global default_timeout
|
||||
default_timeout = timeout
|
||||
|
||||
|
||||
def getdefaulttimeout():
|
||||
"""
|
||||
Return the global timeout setting(second) to connect.
|
||||
"""
|
||||
return default_timeout
|
||||
|
||||
|
||||
def _parse_url(url):
|
||||
"""
|
||||
parse url and the result is tuple of
|
||||
(hostname, port, resource path and the flag of secure mode)
|
||||
|
||||
url: url string.
|
||||
"""
|
||||
if ":" not in url:
|
||||
raise ValueError("url is invalid")
|
||||
|
||||
scheme, url = url.split(":", 1)
|
||||
|
||||
parsed = urlparse(url, scheme="http")
|
||||
if parsed.hostname:
|
||||
hostname = parsed.hostname
|
||||
else:
|
||||
raise ValueError("hostname is invalid")
|
||||
port = 0
|
||||
if parsed.port:
|
||||
port = parsed.port
|
||||
|
||||
is_secure = False
|
||||
if scheme == "ws":
|
||||
if not port:
|
||||
port = 80
|
||||
elif scheme == "wss":
|
||||
is_secure = True
|
||||
if not port:
|
||||
port = 443
|
||||
else:
|
||||
raise ValueError("scheme %s is invalid" % scheme)
|
||||
|
||||
if parsed.path:
|
||||
resource = parsed.path
|
||||
else:
|
||||
resource = "/"
|
||||
|
||||
if parsed.query:
|
||||
resource += "?" + parsed.query
|
||||
|
||||
return (hostname, port, resource, is_secure)
|
||||
|
||||
|
||||
def create_connection(url, timeout=None, **options):
|
||||
"""
|
||||
connect to url and return websocket object.
|
||||
|
||||
Connect to url and return the WebSocket object.
|
||||
Passing optional timeout parameter will set the timeout on the socket.
|
||||
If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used.
|
||||
You can customize using 'options'.
|
||||
If you set "header" list object, you can set your own custom header.
|
||||
|
||||
>>> conn = create_connection("ws://echo.websocket.org/",
|
||||
... header=["User-Agent: MyProgram",
|
||||
... "x-custom: header"])
|
||||
|
||||
|
||||
timeout: socket timeout time. This value is integer.
|
||||
if you set None for this value, it means "use default_timeout value"
|
||||
|
||||
options: current support option is only "header".
|
||||
if you set header as dict value, the custom HTTP headers are added.
|
||||
"""
|
||||
sockopt = options.get("sockopt", [])
|
||||
sslopt = options.get("sslopt", {})
|
||||
websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
|
||||
websock.settimeout(timeout if timeout is not None else default_timeout)
|
||||
websock.connect(url, **options)
|
||||
return websock
|
||||
|
||||
_MAX_INTEGER = (1 << 32) -1
|
||||
_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
|
||||
_MAX_CHAR_BYTE = (1<<8) -1
|
||||
|
||||
# ref. Websocket gets an update, and it breaks stuff.
|
||||
# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
|
||||
|
||||
|
||||
def _create_sec_websocket_key():
|
||||
uid = uuid.uuid4()
|
||||
return base64.encodestring(uid.bytes).strip()
|
||||
|
||||
|
||||
_HEADERS_TO_CHECK = {
|
||||
"upgrade": "websocket",
|
||||
"connection": "upgrade",
|
||||
}
|
||||
|
||||
|
||||
class ABNF(object):
|
||||
"""
|
||||
ABNF frame class.
|
||||
see http://tools.ietf.org/html/rfc5234
|
||||
and http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
"""
|
||||
|
||||
# operation code values.
|
||||
OPCODE_CONT = 0x0
|
||||
OPCODE_TEXT = 0x1
|
||||
OPCODE_BINARY = 0x2
|
||||
OPCODE_CLOSE = 0x8
|
||||
OPCODE_PING = 0x9
|
||||
OPCODE_PONG = 0xa
|
||||
|
||||
# available operation code value tuple
|
||||
OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
|
||||
OPCODE_PING, OPCODE_PONG)
|
||||
|
||||
# opcode human readable string
|
||||
OPCODE_MAP = {
|
||||
OPCODE_CONT: "cont",
|
||||
OPCODE_TEXT: "text",
|
||||
OPCODE_BINARY: "binary",
|
||||
OPCODE_CLOSE: "close",
|
||||
OPCODE_PING: "ping",
|
||||
OPCODE_PONG: "pong"
|
||||
}
|
||||
|
||||
# data length threashold.
|
||||
LENGTH_7 = 0x7d
|
||||
LENGTH_16 = 1 << 16
|
||||
LENGTH_63 = 1 << 63
|
||||
|
||||
def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
|
||||
opcode=OPCODE_TEXT, mask=1, data=""):
|
||||
"""
|
||||
Constructor for ABNF.
|
||||
please check RFC for arguments.
|
||||
"""
|
||||
self.fin = fin
|
||||
self.rsv1 = rsv1
|
||||
self.rsv2 = rsv2
|
||||
self.rsv3 = rsv3
|
||||
self.opcode = opcode
|
||||
self.mask = mask
|
||||
self.data = data
|
||||
self.get_mask_key = os.urandom
|
||||
|
||||
def __str__(self):
|
||||
return "fin=" + str(self.fin) \
|
||||
+ " opcode=" + str(self.opcode) \
|
||||
+ " data=" + str(self.data)
|
||||
|
||||
@staticmethod
|
||||
def create_frame(data, opcode):
|
||||
"""
|
||||
create frame to send text, binary and other data.
|
||||
|
||||
data: data to send. This is string value(byte array).
|
||||
if opcode is OPCODE_TEXT and this value is uniocde,
|
||||
data value is conveted into unicode string, automatically.
|
||||
|
||||
opcode: operation code. please see OPCODE_XXX.
|
||||
"""
|
||||
if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
|
||||
data = data.encode("utf-8")
|
||||
# mask must be set if send data from client
|
||||
return ABNF(1, 0, 0, 0, opcode, 1, data)
|
||||
|
||||
def format(self):
|
||||
"""
|
||||
format this object to string(byte array) to send data to server.
|
||||
"""
|
||||
if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
|
||||
raise ValueError("not 0 or 1")
|
||||
if self.opcode not in ABNF.OPCODES:
|
||||
raise ValueError("Invalid OPCODE")
|
||||
length = len(self.data)
|
||||
if length >= ABNF.LENGTH_63:
|
||||
raise ValueError("data is too long")
|
||||
|
||||
frame_header = chr(self.fin << 7
|
||||
| self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
|
||||
| self.opcode)
|
||||
if length < ABNF.LENGTH_7:
|
||||
frame_header += chr(self.mask << 7 | length)
|
||||
elif length < ABNF.LENGTH_16:
|
||||
frame_header += chr(self.mask << 7 | 0x7e)
|
||||
frame_header += struct.pack("!H", length)
|
||||
else:
|
||||
frame_header += chr(self.mask << 7 | 0x7f)
|
||||
frame_header += struct.pack("!Q", length)
|
||||
|
||||
if not self.mask:
|
||||
return frame_header + self.data
|
||||
else:
|
||||
mask_key = self.get_mask_key(4)
|
||||
return frame_header + self._get_masked(mask_key)
|
||||
|
||||
def _get_masked(self, mask_key):
|
||||
s = ABNF.mask(mask_key, self.data)
|
||||
return mask_key + "".join(s)
|
||||
|
||||
@staticmethod
|
||||
def mask(mask_key, data):
|
||||
"""
|
||||
mask or unmask data. Just do xor for each byte
|
||||
|
||||
mask_key: 4 byte string(byte).
|
||||
|
||||
data: data to mask/unmask.
|
||||
"""
|
||||
_m = array.array("B", mask_key)
|
||||
_d = array.array("B", data)
|
||||
for i in xrange(len(_d)):
|
||||
_d[i] ^= _m[i % 4]
|
||||
return _d.tostring()
|
||||
|
||||
|
||||
class WebSocket(object):
|
||||
"""
|
||||
Low level WebSocket interface.
|
||||
This class is based on
|
||||
The WebSocket protocol draft-hixie-thewebsocketprotocol-76
|
||||
http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
||||
|
||||
We can connect to the websocket server and send/recieve data.
|
||||
The following example is a echo client.
|
||||
|
||||
>>> import websocket
|
||||
>>> ws = websocket.WebSocket()
|
||||
>>> ws.connect("ws://echo.websocket.org")
|
||||
>>> ws.send("Hello, Server")
|
||||
>>> ws.recv()
|
||||
'Hello, Server'
|
||||
>>> ws.close()
|
||||
|
||||
get_mask_key: a callable to produce new mask keys, see the set_mask_key
|
||||
function's docstring for more details
|
||||
sockopt: values for socket.setsockopt.
|
||||
sockopt must be tuple and each element is argument of sock.setscokopt.
|
||||
sslopt: dict object for ssl socket option.
|
||||
"""
|
||||
|
||||
def __init__(self, get_mask_key=None, sockopt=None, sslopt=None):
|
||||
"""
|
||||
Initalize WebSocket object.
|
||||
"""
|
||||
if sockopt is None:
|
||||
sockopt = []
|
||||
if sslopt is None:
|
||||
sslopt = {}
|
||||
self.connected = False
|
||||
self.sock = socket.socket()
|
||||
for opts in sockopt:
|
||||
self.sock.setsockopt(*opts)
|
||||
self.sslopt = sslopt
|
||||
self.get_mask_key = get_mask_key
|
||||
# Buffers over the packets from the layer beneath until desired amount
|
||||
# bytes of bytes are received.
|
||||
self._recv_buffer = []
|
||||
# These buffer over the build-up of a single frame.
|
||||
self._frame_header = None
|
||||
self._frame_length = None
|
||||
self._frame_mask = None
|
||||
self._cont_data = None
|
||||
|
||||
def fileno(self):
|
||||
return self.sock.fileno()
|
||||
|
||||
def set_mask_key(self, func):
|
||||
"""
|
||||
set function to create musk key. You can custumize mask key generator.
|
||||
Mainly, this is for testing purpose.
|
||||
|
||||
func: callable object. the fuct must 1 argument as integer.
|
||||
The argument means length of mask key.
|
||||
This func must be return string(byte array),
|
||||
which length is argument specified.
|
||||
"""
|
||||
self.get_mask_key = func
|
||||
|
||||
def gettimeout(self):
|
||||
"""
|
||||
Get the websocket timeout(second).
|
||||
"""
|
||||
return self.sock.gettimeout()
|
||||
|
||||
def settimeout(self, timeout):
|
||||
"""
|
||||
Set the timeout to the websocket.
|
||||
|
||||
timeout: timeout time(second).
|
||||
"""
|
||||
self.sock.settimeout(timeout)
|
||||
|
||||
timeout = property(gettimeout, settimeout)
|
||||
|
||||
def connect(self, url, **options):
|
||||
"""
|
||||
Connect to url. url is websocket url scheme. ie. ws://host:port/resource
|
||||
You can customize using 'options'.
|
||||
If you set "header" dict object, you can set your own custom header.
|
||||
|
||||
>>> ws = WebSocket()
|
||||
>>> ws.connect("ws://echo.websocket.org/",
|
||||
... header={"User-Agent: MyProgram",
|
||||
... "x-custom: header"})
|
||||
|
||||
timeout: socket timeout time. This value is integer.
|
||||
if you set None for this value,
|
||||
it means "use default_timeout value"
|
||||
|
||||
options: current support option is only "header".
|
||||
if you set header as dict value,
|
||||
the custom HTTP headers are added.
|
||||
|
||||
"""
|
||||
hostname, port, resource, is_secure = _parse_url(url)
|
||||
# TODO: we need to support proxy
|
||||
self.sock.connect((hostname, port))
|
||||
if is_secure:
|
||||
if HAVE_SSL:
|
||||
if self.sslopt is None:
|
||||
sslopt = {}
|
||||
else:
|
||||
sslopt = self.sslopt
|
||||
self.sock = ssl.wrap_socket(self.sock, **sslopt)
|
||||
else:
|
||||
raise WebSocketException("SSL not available.")
|
||||
|
||||
self._handshake(hostname, port, resource, **options)
|
||||
|
||||
def _handshake(self, host, port, resource, **options):
|
||||
sock = self.sock
|
||||
headers = []
|
||||
headers.append("GET %s HTTP/1.1" % resource)
|
||||
headers.append("Upgrade: websocket")
|
||||
headers.append("Connection: Upgrade")
|
||||
if port == 80:
|
||||
hostport = host
|
||||
else:
|
||||
hostport = "%s:%d" % (host, port)
|
||||
headers.append("Host: %s" % hostport)
|
||||
|
||||
if "origin" in options:
|
||||
headers.append("Origin: %s" % options["origin"])
|
||||
else:
|
||||
headers.append("Origin: http://%s" % hostport)
|
||||
|
||||
key = _create_sec_websocket_key()
|
||||
headers.append("Sec-WebSocket-Key: %s" % key)
|
||||
headers.append("Sec-WebSocket-Version: %s" % VERSION)
|
||||
if "header" in options:
|
||||
headers.extend(options["header"])
|
||||
|
||||
headers.append("")
|
||||
headers.append("")
|
||||
|
||||
header_str = "\r\n".join(headers)
|
||||
self._send(header_str)
|
||||
if traceEnabled:
|
||||
logger.debug("--- request header ---")
|
||||
logger.debug(header_str)
|
||||
logger.debug("-----------------------")
|
||||
|
||||
status, resp_headers = self._read_headers()
|
||||
if status != 101:
|
||||
self.close()
|
||||
raise WebSocketException("Handshake Status %d" % status)
|
||||
|
||||
success = self._validate_header(resp_headers, key)
|
||||
if not success:
|
||||
self.close()
|
||||
raise WebSocketException("Invalid WebSocket Header")
|
||||
|
||||
self.connected = True
|
||||
|
||||
def _validate_header(self, headers, key):
|
||||
for k, v in _HEADERS_TO_CHECK.iteritems():
|
||||
r = headers.get(k, None)
|
||||
if not r:
|
||||
return False
|
||||
r = r.lower()
|
||||
if v != r:
|
||||
return False
|
||||
|
||||
result = headers.get("sec-websocket-accept", None)
|
||||
if not result:
|
||||
return False
|
||||
result = result.lower()
|
||||
|
||||
value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower()
|
||||
return hashed == result
|
||||
|
||||
def _read_headers(self):
|
||||
status = None
|
||||
headers = {}
|
||||
if traceEnabled:
|
||||
logger.debug("--- response header ---")
|
||||
|
||||
while True:
|
||||
line = self._recv_line()
|
||||
if line == "\r\n":
|
||||
break
|
||||
line = line.strip()
|
||||
if traceEnabled:
|
||||
logger.debug(line)
|
||||
if not status:
|
||||
status_info = line.split(" ", 2)
|
||||
status = int(status_info[1])
|
||||
else:
|
||||
kv = line.split(":", 1)
|
||||
if len(kv) == 2:
|
||||
key, value = kv
|
||||
headers[key.lower()] = value.strip().lower()
|
||||
else:
|
||||
raise WebSocketException("Invalid header")
|
||||
|
||||
if traceEnabled:
|
||||
logger.debug("-----------------------")
|
||||
|
||||
return status, headers
|
||||
|
||||
def send(self, payload, opcode=ABNF.OPCODE_TEXT):
|
||||
"""
|
||||
Send the data as string.
|
||||
|
||||
payload: Payload must be utf-8 string or unicoce,
|
||||
if the opcode is OPCODE_TEXT.
|
||||
Otherwise, it must be string(byte array)
|
||||
|
||||
opcode: operation code to send. Please see OPCODE_XXX.
|
||||
"""
|
||||
frame = ABNF.create_frame(payload, opcode)
|
||||
if self.get_mask_key:
|
||||
frame.get_mask_key = self.get_mask_key
|
||||
data = frame.format()
|
||||
length = len(data)
|
||||
if traceEnabled:
|
||||
logger.debug("send: " + repr(data))
|
||||
while data:
|
||||
l = self._send(data)
|
||||
data = data[l:]
|
||||
return length
|
||||
|
||||
def send_binary(self, payload):
|
||||
return self.send(payload, ABNF.OPCODE_BINARY)
|
||||
|
||||
def ping(self, payload=""):
|
||||
"""
|
||||
send ping data.
|
||||
|
||||
payload: data payload to send server.
|
||||
"""
|
||||
self.send(payload, ABNF.OPCODE_PING)
|
||||
|
||||
def pong(self, payload):
|
||||
"""
|
||||
send pong data.
|
||||
|
||||
payload: data payload to send server.
|
||||
"""
|
||||
self.send(payload, ABNF.OPCODE_PONG)
|
||||
|
||||
def recv(self):
|
||||
"""
|
||||
Receive string data(byte array) from the server.
|
||||
|
||||
return value: string(byte array) value.
|
||||
"""
|
||||
opcode, data = self.recv_data()
|
||||
return data
|
||||
|
||||
def recv_data(self):
|
||||
"""
|
||||
Recieve data with operation code.
|
||||
|
||||
return value: tuple of operation code and string(byte array) value.
|
||||
"""
|
||||
while True:
|
||||
frame = self.recv_frame()
|
||||
if not frame:
|
||||
# handle error:
|
||||
# 'NoneType' object has no attribute 'opcode'
|
||||
raise WebSocketException("Not a valid frame %s" % frame)
|
||||
elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
|
||||
if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data:
|
||||
raise WebSocketException("Illegal frame")
|
||||
if self._cont_data:
|
||||
self._cont_data[1] += frame.data
|
||||
else:
|
||||
self._cont_data = [frame.opcode, frame.data]
|
||||
|
||||
if frame.fin:
|
||||
data = self._cont_data
|
||||
self._cont_data = None
|
||||
return data
|
||||
elif frame.opcode == ABNF.OPCODE_CLOSE:
|
||||
self.send_close()
|
||||
return (frame.opcode, None)
|
||||
elif frame.opcode == ABNF.OPCODE_PING:
|
||||
self.pong(frame.data)
|
||||
|
||||
def recv_frame(self):
|
||||
"""
|
||||
recieve data as frame from server.
|
||||
|
||||
return value: ABNF frame object.
|
||||
"""
|
||||
# Header
|
||||
if self._frame_header is None:
|
||||
self._frame_header = self._recv_strict(2)
|
||||
b1 = ord(self._frame_header[0])
|
||||
fin = b1 >> 7 & 1
|
||||
rsv1 = b1 >> 6 & 1
|
||||
rsv2 = b1 >> 5 & 1
|
||||
rsv3 = b1 >> 4 & 1
|
||||
opcode = b1 & 0xf
|
||||
b2 = ord(self._frame_header[1])
|
||||
has_mask = b2 >> 7 & 1
|
||||
# Frame length
|
||||
if self._frame_length is None:
|
||||
length_bits = b2 & 0x7f
|
||||
if length_bits == 0x7e:
|
||||
length_data = self._recv_strict(2)
|
||||
self._frame_length = struct.unpack("!H", length_data)[0]
|
||||
elif length_bits == 0x7f:
|
||||
length_data = self._recv_strict(8)
|
||||
self._frame_length = struct.unpack("!Q", length_data)[0]
|
||||
else:
|
||||
self._frame_length = length_bits
|
||||
# Mask
|
||||
if self._frame_mask is None:
|
||||
self._frame_mask = self._recv_strict(4) if has_mask else ""
|
||||
# Payload
|
||||
payload = self._recv_strict(self._frame_length)
|
||||
if has_mask:
|
||||
payload = ABNF.mask(self._frame_mask, payload)
|
||||
# Reset for next frame
|
||||
self._frame_header = None
|
||||
self._frame_length = None
|
||||
self._frame_mask = None
|
||||
return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
|
||||
|
||||
|
||||
def send_close(self, status=STATUS_NORMAL, reason=""):
|
||||
"""
|
||||
send close data to the server.
|
||||
|
||||
status: status code to send. see STATUS_XXX.
|
||||
|
||||
reason: the reason to close. This must be string.
|
||||
"""
|
||||
if status < 0 or status >= ABNF.LENGTH_16:
|
||||
raise ValueError("code is invalid range")
|
||||
self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
|
||||
|
||||
def close(self, status=STATUS_NORMAL, reason=""):
|
||||
"""
|
||||
Close Websocket object
|
||||
|
||||
status: status code to send. see STATUS_XXX.
|
||||
|
||||
reason: the reason to close. This must be string.
|
||||
"""
|
||||
if self.connected:
|
||||
if status < 0 or status >= ABNF.LENGTH_16:
|
||||
raise ValueError("code is invalid range")
|
||||
|
||||
try:
|
||||
self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
|
||||
timeout = self.sock.gettimeout()
|
||||
self.sock.settimeout(3)
|
||||
try:
|
||||
frame = self.recv_frame()
|
||||
if logger.isEnabledFor(logging.ERROR):
|
||||
recv_status = struct.unpack("!H", frame.data)[0]
|
||||
if recv_status != STATUS_NORMAL:
|
||||
logger.error("close status: " + repr(recv_status))
|
||||
except:
|
||||
pass
|
||||
self.sock.settimeout(timeout)
|
||||
self.sock.shutdown(socket.SHUT_RDWR)
|
||||
except:
|
||||
pass
|
||||
self._closeInternal()
|
||||
|
||||
def _closeInternal(self):
|
||||
self.connected = False
|
||||
self.sock.close()
|
||||
|
||||
def _send(self, data):
|
||||
try:
|
||||
return self.sock.send(data)
|
||||
except socket.timeout as e:
|
||||
raise WebSocketTimeoutException(e.args[0])
|
||||
except Exception as e:
|
||||
if "timed out" in e.args[0]:
|
||||
raise WebSocketTimeoutException(e.args[0])
|
||||
else:
|
||||
raise e
|
||||
|
||||
def _recv(self, bufsize):
|
||||
try:
|
||||
bytes = self.sock.recv(bufsize)
|
||||
except socket.timeout as e:
|
||||
raise WebSocketTimeoutException(e.args[0])
|
||||
except SSLError as e:
|
||||
if e.args[0] == "The read operation timed out":
|
||||
raise WebSocketTimeoutException(e.args[0])
|
||||
else:
|
||||
raise
|
||||
if not bytes:
|
||||
raise WebSocketConnectionClosedException()
|
||||
return bytes
|
||||
|
||||
|
||||
def _recv_strict(self, bufsize):
|
||||
shortage = bufsize - sum(len(x) for x in self._recv_buffer)
|
||||
while shortage > 0:
|
||||
bytes = self._recv(shortage)
|
||||
self._recv_buffer.append(bytes)
|
||||
shortage -= len(bytes)
|
||||
unified = "".join(self._recv_buffer)
|
||||
if shortage == 0:
|
||||
self._recv_buffer = []
|
||||
return unified
|
||||
else:
|
||||
self._recv_buffer = [unified[bufsize:]]
|
||||
return unified[:bufsize]
|
||||
|
||||
|
||||
def _recv_line(self):
|
||||
line = []
|
||||
while True:
|
||||
c = self._recv(1)
|
||||
line.append(c)
|
||||
if c == "\n":
|
||||
break
|
||||
return "".join(line)
|
||||
|
||||
|
||||
class WebSocketApp(object):
|
||||
"""
|
||||
Higher level of APIs are provided.
|
||||
The interface is like JavaScript WebSocket object.
|
||||
"""
|
||||
def __init__(self, url, header=[],
|
||||
on_open=None, on_message=None, on_error=None,
|
||||
on_close=None, keep_running=True, get_mask_key=None):
|
||||
"""
|
||||
url: websocket url.
|
||||
header: custom header for websocket handshake.
|
||||
on_open: callable object which is called at opening websocket.
|
||||
this function has one argument. The arugment is this class object.
|
||||
on_message: callbale object which is called when recieved data.
|
||||
on_message has 2 arguments.
|
||||
The 1st arugment is this class object.
|
||||
The passing 2nd arugment is utf-8 string which we get from the server.
|
||||
on_error: callable object which is called when we get error.
|
||||
on_error has 2 arguments.
|
||||
The 1st arugment is this class object.
|
||||
The passing 2nd arugment is exception object.
|
||||
on_close: callable object which is called when closed the connection.
|
||||
this function has one argument. The arugment is this class object.
|
||||
keep_running: a boolean flag indicating whether the app's main loop should
|
||||
keep running, defaults to True
|
||||
get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's
|
||||
docstring for more information
|
||||
"""
|
||||
self.url = url
|
||||
self.header = header
|
||||
self.on_open = on_open
|
||||
self.on_message = on_message
|
||||
self.on_error = on_error
|
||||
self.on_close = on_close
|
||||
self.keep_running = keep_running
|
||||
self.get_mask_key = get_mask_key
|
||||
self.sock = None
|
||||
|
||||
def send(self, data, opcode=ABNF.OPCODE_TEXT):
|
||||
"""
|
||||
send message.
|
||||
data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode.
|
||||
opcode: operation code of data. default is OPCODE_TEXT.
|
||||
"""
|
||||
if self.sock.send(data, opcode) == 0:
|
||||
raise WebSocketConnectionClosedException()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
close websocket connection.
|
||||
"""
|
||||
self.keep_running = False
|
||||
self.sock.close()
|
||||
|
||||
def _send_ping(self, interval):
|
||||
while True:
|
||||
for i in range(interval):
|
||||
time.sleep(1)
|
||||
if not self.keep_running:
|
||||
return
|
||||
self.sock.ping()
|
||||
|
||||
def run_forever(self, sockopt=None, sslopt=None, ping_interval=0):
|
||||
"""
|
||||
run event loop for WebSocket framework.
|
||||
This loop is infinite loop and is alive during websocket is available.
|
||||
sockopt: values for socket.setsockopt.
|
||||
sockopt must be tuple and each element is argument of sock.setscokopt.
|
||||
sslopt: ssl socket optional dict.
|
||||
ping_interval: automatically send "ping" command every specified period(second)
|
||||
if set to 0, not send automatically.
|
||||
"""
|
||||
if sockopt is None:
|
||||
sockopt = []
|
||||
if sslopt is None:
|
||||
sslopt = {}
|
||||
if self.sock:
|
||||
raise WebSocketException("socket is already opened")
|
||||
thread = None
|
||||
|
||||
try:
|
||||
self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt)
|
||||
self.sock.settimeout(2)#default_timeout)
|
||||
self.sock.connect(self.url, header=self.header)
|
||||
self._callback(self.on_open)
|
||||
|
||||
if ping_interval:
|
||||
thread = threading.Thread(target=self._send_ping, args=(ping_interval,))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
while self.keep_running:
|
||||
|
||||
try:
|
||||
data = self.sock.recv()
|
||||
|
||||
if data is None or self.keep_running == False:
|
||||
break
|
||||
self._callback(self.on_message, data)
|
||||
|
||||
except Exception, e:
|
||||
#print str(e.args[0])
|
||||
if "timed out" not in e.args[0]:
|
||||
raise e
|
||||
|
||||
except Exception, e:
|
||||
self._callback(self.on_error, e)
|
||||
finally:
|
||||
if thread:
|
||||
self.keep_running = False
|
||||
self.sock.close()
|
||||
self._callback(self.on_close)
|
||||
self.sock = None
|
||||
|
||||
def _callback(self, callback, *args):
|
||||
if callback:
|
||||
try:
|
||||
callback(self, *args)
|
||||
except Exception, e:
|
||||
logger.error(e)
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
_, _, tb = sys.exc_info()
|
||||
traceback.print_tb(tb)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
enableTrace(True)
|
||||
ws = create_connection("ws://echo.websocket.org/")
|
||||
print("Sending 'Hello, World'...")
|
||||
ws.send("Hello, World")
|
||||
print("Sent")
|
||||
print("Receiving...")
|
||||
result = ws.recv()
|
||||
print("Received '%s'" % result)
|
||||
ws.close()
|
BIN
resources/mb3.png
Normal file
BIN
resources/mb3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
BIN
resources/media/BlankPoster.png
Normal file
BIN
resources/media/BlankPoster.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
66
resources/settings.xml
Normal file
66
resources/settings.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<settings>
|
||||
<category label="30014"> <!-- MediaBrowser -->
|
||||
<setting id="ipaddress" type="text" label="30000" default="<none>" visible="true" enable="true" />
|
||||
<setting id="port" type="text" label="30030" default="8096" visible="true" enable="true" />
|
||||
<setting type="sep" />
|
||||
<setting id="username" type="text" label="30024" />
|
||||
<setting id="password" type="text" option="hidden" label="30025" />
|
||||
</category>
|
||||
<category label="30015"> <!-- Network -->
|
||||
<setting id="smbusername" type="text" label="30007" default="" visible="true" enable="true" />
|
||||
<setting id="smbpassword" type="text" label="30008" default="" option="hidden" visible="true" enable="true" />
|
||||
<setting id="videoBitRate" type="enum" label="30160" values="664 Kbps SD|996 Kbps HD|1.3 Mbps HD|2.0 Mbps HD|3.2 Mbps HD|4.7 Mbps HD|6.2 Mbps HD|7.7 Mbps HD|9.2 Mbps HD|10.7 Mbps HD|12.2 Mbps HD|13.7 Mbps HD|15.2 Mbps HD|16.7 Mbps HD|18.2 Mbps HD|20.0 Mbps HD|40.0 Mbps HD|100.0 Mbps HD [default]|1000.0 Mbps HD" default="17" />
|
||||
<setting id="deviceName" type="text" label="30016" default="XBMB3C" visible="true" enable="true" />
|
||||
<setting id="playFromStream" type="bool" label="30002" default="false" visible="true" enable="true" />
|
||||
</category>
|
||||
<category label="30110"> <!-- Interface -->
|
||||
<setting id="selectAction" type="enum" label="30151" values="Play|Info" default="0" />
|
||||
<setting id="resumeJumpBack" type="number" label="30114" default="10" visible="true" enable="true" />
|
||||
<setting id="markPlayedAt" type="number" label="30115" default="90" 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="flattenSeasons" type="bool" label="30163" default="false" visible="true" enable="true" />
|
||||
<setting id="autoEnterSingle" type="bool" label="30001" default="true" visible="true" enable="true" />
|
||||
<setting id="sortNextUp" type="bool" label="30156" default="false" visible="true" enable="true" />
|
||||
<setting id="offerDelete" type="bool" label="30127" default="false" visible="true" enable="true" />
|
||||
<setting id="showLoadProgress" type="bool" label="30120" default="false" visible="true" enable="true" />
|
||||
</category>
|
||||
<category label="30158"> <!-- Metadata -->
|
||||
<setting id="includeStreamInfo" type="bool" label="30111" default="true" visible="true" enable="true" />
|
||||
<setting id="includePeople" type="bool" label="30112" default="true" visible="true" enable="true" />
|
||||
<setting id="includeOverview" type="bool" label="30113" default="true" visible="true" enable="true" />
|
||||
<setting id="numRecentMovies" type="number" label="30036" default="20" visible="true" enable="true" />
|
||||
<setting id="numRecentTV" type="number" label="30037" default="20" visible="true" enable="true" />
|
||||
<setting id="numRecentMusic" type="number" label="30035" default="20" visible="true" enable="true" />
|
||||
</category>
|
||||
<category label="30159"> <!-- Artwork -->
|
||||
<setting id="backgroundRefresh" type="number" label="30117" default="30" visible="true" enable="true" />
|
||||
<setting id="useSeasonPoster" type="bool" label="30039" default="false" visible="true" enable="true" />
|
||||
<setting id="disableCoverArt" type="bool" label="30157" default="false" visible="true" enable="true" />
|
||||
<setting id="showIndicators" type="bool" label="30152" default="false" visible="true" enable="true" />
|
||||
<setting id="showWatchedIndicators" type="bool" label="30153" default="true" visible="eq(-1,true)" enable="eq(-1,true)" />
|
||||
<setting id="showUnplayedIndicators" type="bool" label="30154" default="true" visible="eq(-2,true)" enable="eq(-2,true)" />
|
||||
<setting id="showPlayedPrecentageIndicators" type="bool" label="30155" default="true" visible="eq(-3,true)" enable="eq(-3,true)" />
|
||||
<setting id="useThemeMusic" type="bool" label="30139" default="true" visible="true" enable="true" />
|
||||
<setting id="loopThemeMusic" type="bool" label="30140" default="true" visible="true" enable="true" />
|
||||
</category>
|
||||
<category label="30142"> <!-- Services -->
|
||||
<setting id="useBackgroundLoader" type="bool" label="30141" default="true" visible="true" enable="true" />
|
||||
<setting id="useInfoLoader" type="bool" label="30143" default="true" visible="true" enable="true" />
|
||||
<setting id="useMenuLoader" type="bool" label="30144" default="true" visible="true" enable="true" />
|
||||
<setting id="useWebSocketRemote" type="bool" label="30145" default="true" visible="true" enable="true" />
|
||||
<setting id="useInProgressUpdater" type="bool" label="30146" default="true" visible="true" enable="true" />
|
||||
<setting id="useRecentInfoUpdater" type="bool" label="30147" default="true" visible="true" enable="true" />
|
||||
<setting id="useRandomInfo" type="bool" label="30148" default="true" visible="true" enable="true" />
|
||||
<setting id="useNextUp" type="bool" label="30149" default="true" visible="true" enable="true" />
|
||||
<setting id="useSuggested" type="bool" label="30161" default="true" visible="true" enable="true" />
|
||||
</category>
|
||||
<category label="30022"> <!-- Advanced -->
|
||||
<setting id="logLevel" type="enum" label="30004" values="None(0)|Info(1)|Debug(2)" default="0" />
|
||||
<setting id="profile" type="bool" label="30010" default="false" visible="true" enable="true" />
|
||||
<!--setting id="useJson" type="bool" label="30026" default="false" visible="true" enable="true" /> -->
|
||||
</category>
|
||||
</settings>
|
415
resources/skins/default/720p/ItemInfo.xml
Normal file
415
resources/skins/default/720p/ItemInfo.xml
Normal file
@ -0,0 +1,415 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<window id="3300" type="dialog">
|
||||
<defaultcontrol always="true">3002</defaultcontrol>
|
||||
<zorder>2</zorder>
|
||||
<coordinates>
|
||||
<system>1</system>
|
||||
<left>120</left>
|
||||
<top>50</top>
|
||||
</coordinates>
|
||||
<include>dialogeffect</include>
|
||||
<controls>
|
||||
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>1040</width>
|
||||
<height>600</height>
|
||||
<texture border="40">DialogBack.png</texture>
|
||||
</control>
|
||||
|
||||
<control type="image" id="3001">
|
||||
<left>20</left>
|
||||
<top>20</top>
|
||||
<width>1000</width>
|
||||
<height>560</height>
|
||||
<colordiffuse>FF444444</colordiffuse>
|
||||
</control>
|
||||
|
||||
<control type="label" id="3000">
|
||||
<left>30</left>
|
||||
<top>25</top>
|
||||
<width>950</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<label>-</label>
|
||||
<font>font24_title</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
</control>
|
||||
|
||||
<control type="label" id="3003">
|
||||
<left>30</left>
|
||||
<top>55</top>
|
||||
<width>300</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<label>-</label>
|
||||
<font>font18_title</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
</control>
|
||||
|
||||
<!-- episode image 16x9 -->
|
||||
<control type="image" id="3009">
|
||||
<left>40</left>
|
||||
<top>130</top>
|
||||
<width>250</width>
|
||||
<height>140</height>
|
||||
<aspectratio>stretch</aspectratio>
|
||||
</control>
|
||||
<control type="image" id="3010">
|
||||
<left>40</left>
|
||||
<top>265</top>
|
||||
<width>250</width>
|
||||
<height>5</height>
|
||||
<texture background="true">-</texture>
|
||||
<colordiffuse>AAFFFFFF</colordiffuse>
|
||||
<aspectratio>stretch</aspectratio>
|
||||
</control>
|
||||
|
||||
<!-- poster image -->
|
||||
<control type="image" id="3011">
|
||||
<left>60</left>
|
||||
<top>100</top>
|
||||
<width>175</width>
|
||||
<height>250</height>
|
||||
<aspectratio>stretch</aspectratio>
|
||||
</control>
|
||||
<control type="image" id="3012">
|
||||
<left>60</left>
|
||||
<top>345</top>
|
||||
<width>175</width>
|
||||
<height>5</height>
|
||||
<texture background="true">-</texture>
|
||||
<colordiffuse>AAFFFFFF</colordiffuse>
|
||||
<aspectratio>stretch</aspectratio>
|
||||
</control>
|
||||
|
||||
<control type="list" id="3220">
|
||||
<left>30</left>
|
||||
<top>380</top>
|
||||
<width>240</width>
|
||||
<height>120</height>
|
||||
<onleft>3002</onleft>
|
||||
<onright>3221</onright>
|
||||
<onup>3235</onup>
|
||||
<ondown>3002</ondown>
|
||||
<pagecontrol>3221</pagecontrol>
|
||||
<scrolltime>200</scrolltime>
|
||||
<itemlayout height="20">
|
||||
<control type="label">
|
||||
<left>60</left>
|
||||
<top>0</top>
|
||||
<width>60</width>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<align>right</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>blue</textcolor>
|
||||
<selectedcolor>selected</selectedcolor>
|
||||
<info>ListItem.Label</info>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>65</left>
|
||||
<top>0</top>
|
||||
<width>180</width>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<selectedcolor>white</selectedcolor>
|
||||
<info>ListItem.Label2</info>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout height="20">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>240</width>
|
||||
<height>20</height>
|
||||
<visible>Control.HasFocus(3220)</visible>
|
||||
<texture>MenuItemFO.png</texture>
|
||||
<include>VisibleFadeEffect</include>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>60</left>
|
||||
<top>0</top>
|
||||
<width>60</width>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<align>right</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>blue</textcolor>
|
||||
<selectedcolor>selected</selectedcolor>
|
||||
<info>ListItem.Label</info>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>65</left>
|
||||
<top>0</top>
|
||||
<width>180</width>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<selectedcolor>white</selectedcolor>
|
||||
<info>ListItem.Label2</info>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
<control type="scrollbar" id="3221">
|
||||
<left>270</left>
|
||||
<top>380</top>
|
||||
<width>20</width>
|
||||
<height>120</height>
|
||||
<texturesliderbackground border="0,14,0,14">ScrollBarV.png</texturesliderbackground>
|
||||
<texturesliderbar border="2,16,2,16">ScrollBarV_bar.png</texturesliderbar>
|
||||
<texturesliderbarfocus border="2,16,2,16">ScrollBarV_bar_focus.png</texturesliderbarfocus>
|
||||
<textureslidernib>ScrollBarNib.png</textureslidernib>
|
||||
<textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus>
|
||||
<onleft>3220</onleft>
|
||||
<onright>3226</onright>
|
||||
<showonepage>false</showonepage>
|
||||
<orientation>vertical</orientation>
|
||||
</control>
|
||||
|
||||
<control type="list" id="3226">
|
||||
<left>310</left>
|
||||
<top>380</top>
|
||||
<width>415</width>
|
||||
<height>120</height>
|
||||
<onleft>3221</onleft>
|
||||
<onright>3235</onright>
|
||||
<onup>3235</onup>
|
||||
<ondown>3002</ondown>
|
||||
<pagecontrol>-</pagecontrol>
|
||||
<scrolltime>200</scrolltime>
|
||||
<itemlayout height="20">
|
||||
<control type="label">
|
||||
<left>70</left>
|
||||
<top>0</top>
|
||||
<width>70</width>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<align>right</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>blue</textcolor>
|
||||
<selectedcolor>selected</selectedcolor>
|
||||
<info>ListItem.Label</info>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>75</left>
|
||||
<top>0</top>
|
||||
<width>340</width>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<selectedcolor>white</selectedcolor>
|
||||
<info>ListItem.Label2</info>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout height="20">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>400</width>
|
||||
<height>20</height>
|
||||
<visible>Control.HasFocus(3226)</visible>
|
||||
<texture>MenuItemFO.png</texture>
|
||||
<include>VisibleFadeEffect</include>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>70</left>
|
||||
<top>0</top>
|
||||
<width>70</width>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<align>right</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>blue</textcolor>
|
||||
<selectedcolor>selected</selectedcolor>
|
||||
<info>ListItem.Label</info>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>75</left>
|
||||
<top>0</top>
|
||||
<width>340</width>
|
||||
<height>20</height>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<selectedcolor>white</selectedcolor>
|
||||
<info>ListItem.Label2</info>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
<!--
|
||||
<control type="scrollbar" id="3227">
|
||||
<left>270</left>
|
||||
<top>380</top>
|
||||
<width>20</width>
|
||||
<height>120</height>
|
||||
<texturesliderbackground border="0,14,0,14">ScrollBarV.png</texturesliderbackground>
|
||||
<texturesliderbar border="2,16,2,16">ScrollBarV_bar.png</texturesliderbar>
|
||||
<texturesliderbarfocus border="2,16,2,16">ScrollBarV_bar_focus.png</texturesliderbarfocus>
|
||||
<textureslidernib>ScrollBarNib.png</textureslidernib>
|
||||
<textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus>
|
||||
<onleft>3220</onleft>
|
||||
<onright>3235</onright>
|
||||
<showonepage>false</showonepage>
|
||||
<orientation>vertical</orientation>
|
||||
</control>
|
||||
-->
|
||||
|
||||
<control type="textbox" id="3223">
|
||||
<left>320</left>
|
||||
<top>100</top>
|
||||
<width>400</width>
|
||||
<height>250</height>
|
||||
<font>font12</font>
|
||||
<!--<align>justify</align>-->
|
||||
<textcolor>white</textcolor>
|
||||
<pagecontrol>3235</pagecontrol>
|
||||
<visible>true</visible>
|
||||
</control>
|
||||
<control type="scrollbar" id="3235">
|
||||
<left>720</left>
|
||||
<top>100</top>
|
||||
<width>20</width>
|
||||
<height>250</height>
|
||||
<texturesliderbackground border="0,14,0,14">ScrollBarV.png</texturesliderbackground>
|
||||
<texturesliderbar border="2,16,2,16">ScrollBarV_bar.png</texturesliderbar>
|
||||
<texturesliderbarfocus border="2,16,2,16">ScrollBarV_bar_focus.png</texturesliderbarfocus>
|
||||
<textureslidernib>ScrollBarNib.png</textureslidernib>
|
||||
<textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus>
|
||||
<onleft>3226</onleft>
|
||||
<onup>-</onup>
|
||||
<onright>3230</onright>
|
||||
<showonepage>false</showonepage>
|
||||
<orientation>vertical</orientation>
|
||||
</control>
|
||||
|
||||
<control type="list" id="3230">
|
||||
<left>760</left>
|
||||
<top>100</top>
|
||||
<width>245calc</width>
|
||||
<height>450</height>
|
||||
<onleft>3235</onleft>
|
||||
<onright>3231</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>-</ondown>
|
||||
<pagecontrol>3231</pagecontrol>
|
||||
<scrolltime>200</scrolltime>
|
||||
<itemlayout height="60">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>60</width>
|
||||
<height>60</height>
|
||||
<texture fallback="DefaultArtist.png">$INFO[Listitem.Icon]</texture>
|
||||
<aspectratio>scale</aspectratio>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>65</left>
|
||||
<top>0</top>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>blue</textcolor>
|
||||
<selectedcolor>selected</selectedcolor>
|
||||
<info>ListItem.Label</info>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>65</left>
|
||||
<top>30</top>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<selectedcolor>white</selectedcolor>
|
||||
<info>ListItem.Label2</info>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout height="60">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>60</width>
|
||||
<height>60</height>
|
||||
<texture fallback="DefaultArtist.png">$INFO[Listitem.Icon]</texture>
|
||||
<aspectratio>scale</aspectratio>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>60</left>
|
||||
<top>0</top>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
<visible>Control.HasFocus(3230)</visible>
|
||||
<texture>MenuItemFO.png</texture>
|
||||
<include>VisibleFadeEffect</include>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>65</left>
|
||||
<top>0</top>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>blue</textcolor>
|
||||
<selectedcolor>selected</selectedcolor>
|
||||
<info>ListItem.Label</info>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>65</left>
|
||||
<top>30</top>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
<font>font10</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>white</textcolor>
|
||||
<selectedcolor>white</selectedcolor>
|
||||
<info>ListItem.Label2</info>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
<control type="scrollbar" id="3231">
|
||||
<left>985</left>
|
||||
<top>100</top>
|
||||
<width>20</width>
|
||||
<height>450</height>
|
||||
<texturesliderbackground border="0,14,0,14">ScrollBarV.png</texturesliderbackground>
|
||||
<texturesliderbar border="2,16,2,16">ScrollBarV_bar.png</texturesliderbar>
|
||||
<texturesliderbarfocus border="2,16,2,16">ScrollBarV_bar_focus.png</texturesliderbarfocus>
|
||||
<textureslidernib>ScrollBarNib.png</textureslidernib>
|
||||
<textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus>
|
||||
<onleft>3230</onleft>
|
||||
<onright>-</onright>
|
||||
<showonepage>false</showonepage>
|
||||
<orientation>vertical</orientation>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3002">
|
||||
<left>30</left>
|
||||
<top>520</top>
|
||||
<width>150</width>
|
||||
<height>40</height>
|
||||
<align>center</align>
|
||||
<label>Play</label>
|
||||
<font>font13</font>
|
||||
<onleft>-</onleft>
|
||||
<onright>3220</onright>
|
||||
<onup>3220</onup>
|
||||
</control>
|
||||
|
||||
|
||||
</controls>
|
||||
</window>
|
205
resources/skins/default/720p/PersonInfo.xml
Normal file
205
resources/skins/default/720p/PersonInfo.xml
Normal file
@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<window>
|
||||
<defaultcontrol always="true">3010</defaultcontrol>
|
||||
<zorder>2</zorder>
|
||||
<coordinates>
|
||||
<system>1</system>
|
||||
<left>120</left>
|
||||
<top>50</top>
|
||||
</coordinates>
|
||||
<include>dialogeffect</include>
|
||||
<controls>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>1040</width>
|
||||
<height>600</height>
|
||||
<texture border="40">DialogBack.png</texture>
|
||||
</control>
|
||||
|
||||
<control type="image">
|
||||
<left>20</left>
|
||||
<top>20</top>
|
||||
<width>1000</width>
|
||||
<height>560</height>
|
||||
<texture>$INFO[Skin.CurrentTheme,special://skin/backgrounds/,.jpg]</texture>
|
||||
<visible>![Skin.HasSetting(UseCustomBackground) + !IsEmpty(Skin.String(CustomBackgroundPath))]</visible>
|
||||
<include>VisibleFadeEffect</include>
|
||||
<colordiffuse>FF444444</colordiffuse>
|
||||
</control>
|
||||
|
||||
<control type="image">
|
||||
<description>Dialog Header image</description>
|
||||
<left>40</left>
|
||||
<top>16</top>
|
||||
<width>960</width>
|
||||
<height>40</height>
|
||||
<texture>dialogheader.png</texture>
|
||||
</control>
|
||||
|
||||
<control type="label" id="1">
|
||||
<description>header label</description>
|
||||
<left>40</left>
|
||||
<top>20</top>
|
||||
<width>960</width>
|
||||
<height>30</height>
|
||||
<font>font13_title</font>
|
||||
<label>Person Info</label>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>selected</textcolor>
|
||||
<shadowcolor>black</shadowcolor>
|
||||
</control>
|
||||
|
||||
<!--
|
||||
<control type="button" id="8">
|
||||
<description>Close Window button</description>
|
||||
<left>960</left>
|
||||
<top>15</top>
|
||||
<width>64</width>
|
||||
<height>32</height>
|
||||
<label>-</label>
|
||||
<font>-</font>
|
||||
<onclick>PreviousMenu</onclick>
|
||||
<texturefocus>DialogCloseButton-focus.png</texturefocus>
|
||||
<texturenofocus>DialogCloseButton.png</texturenofocus>
|
||||
<onleft>-</onleft>
|
||||
<onright>-</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>3005</ondown>
|
||||
</control>
|
||||
-->
|
||||
|
||||
<control type="label" id="3000">
|
||||
<description>person name</description>
|
||||
<left>30</left>
|
||||
<top>65</top>
|
||||
<width>550</width>
|
||||
<height>100</height>
|
||||
<align>left</align>
|
||||
<label>-</label>
|
||||
<font>font13</font>
|
||||
<textcolor>white</textcolor>
|
||||
</control>
|
||||
|
||||
<control type="image" id="3009">
|
||||
<left>30</left>
|
||||
<top>120</top>
|
||||
<width>250</width>
|
||||
<height>250</height>
|
||||
<aspectratio>keep</aspectratio>
|
||||
</control>
|
||||
|
||||
<control type="textbox" id="3001">
|
||||
<description>text</description>
|
||||
<left>300</left>
|
||||
<top>100</top>
|
||||
<width>630</width>
|
||||
<height>280</height>
|
||||
<align>left</align>
|
||||
<label>-</label>
|
||||
<font>font12</font>
|
||||
<pagecontrol>3005</pagecontrol>
|
||||
</control>
|
||||
<control type="scrollbar" id="3005">
|
||||
<left>940</left>
|
||||
<top>100</top>
|
||||
<width>20</width>
|
||||
<height>280</height>
|
||||
<texturesliderbackground border="0,14,0,14">ScrollBarV.png</texturesliderbackground>
|
||||
<texturesliderbar border="2,16,2,16">ScrollBarV_bar.png</texturesliderbar>
|
||||
<texturesliderbarfocus border="2,16,2,16">ScrollBarV_bar_focus.png</texturesliderbarfocus>
|
||||
<textureslidernib>ScrollBarNib.png</textureslidernib>
|
||||
<textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus>
|
||||
<onup>8</onup>
|
||||
<onleft>3001</onleft>
|
||||
<onright>-</onright>
|
||||
<ondown>3010</ondown>
|
||||
<showonepage>false</showonepage>
|
||||
<orientation>vertical</orientation>
|
||||
</control>
|
||||
|
||||
|
||||
<control type="list" id="3010">
|
||||
<left>40</left>
|
||||
<top>390</top>
|
||||
<width>940</width>
|
||||
<height>170</height>
|
||||
<onleft>-</onleft>
|
||||
<onright>-</onright>
|
||||
<onup>3005</onup>
|
||||
<ondown>3011</ondown>
|
||||
<pagecontrol>3011</pagecontrol>
|
||||
<scrolltime>200</scrolltime>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout width="120">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-nofocus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label2]</label>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout width="120">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-nofocus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-focus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
<visible>Control.HasFocus(3010)</visible>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label2]</label>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
<control type="scrollbar" id="3011">
|
||||
<left>40</left>
|
||||
<top>560</top>
|
||||
<width>940</width>
|
||||
<height>20</height>
|
||||
<texturesliderbackground border="14,0,14,0">ScrollBarH.png</texturesliderbackground>
|
||||
<texturesliderbar border="16,2,16,2">ScrollBarH_bar.png</texturesliderbar>
|
||||
<texturesliderbarfocus border="16,2,16,2">ScrollBarH_bar_focus.png</texturesliderbarfocus>
|
||||
<textureslidernib>ScrollBarNib.png</textureslidernib>
|
||||
<textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus>
|
||||
<onup>3010</onup>
|
||||
<showonepage>false</showonepage>
|
||||
<orientation>horizontal</orientation>
|
||||
</control>
|
||||
|
||||
|
||||
</controls>
|
||||
</window>
|
910
resources/skins/default/720p/SearchDialog.xml
Normal file
910
resources/skins/default/720p/SearchDialog.xml
Normal file
@ -0,0 +1,910 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<window>
|
||||
<defaultcontrol always="true">3020</defaultcontrol>
|
||||
<zorder>2</zorder>
|
||||
<coordinates>
|
||||
<system>1</system>
|
||||
<left>120</left>
|
||||
<top>50</top>
|
||||
</coordinates>
|
||||
<!--<include>dialogeffect</include>-->
|
||||
<controls>
|
||||
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>1040</width>
|
||||
<height>600</height>
|
||||
<texture border="40">DialogBack.png</texture>
|
||||
</control>
|
||||
|
||||
<control type="image">
|
||||
<left>20</left>
|
||||
<top>20</top>
|
||||
<width>1000</width>
|
||||
<height>560</height>
|
||||
<texture>$INFO[Skin.CurrentTheme,special://skin/backgrounds/,.jpg]</texture>
|
||||
<visible>![Skin.HasSetting(UseCustomBackground) + !IsEmpty(Skin.String(CustomBackgroundPath))]</visible>
|
||||
<include>VisibleFadeEffect</include>
|
||||
<colordiffuse>FF444444</colordiffuse>
|
||||
</control>
|
||||
|
||||
<control type="image">
|
||||
<left>25</left>
|
||||
<top>98</top>
|
||||
<width>190</width>
|
||||
<height>30</height>
|
||||
<aspectratio>stretch</aspectratio>
|
||||
<texture border="20">KeyboardEditArea.png</texture>
|
||||
</control>
|
||||
<control type="label" id="3010">
|
||||
<left>30</left>
|
||||
<top>100</top>
|
||||
<width>180</width>
|
||||
<height>40</height>
|
||||
<font>font13</font>
|
||||
<onleft>-</onleft>
|
||||
<onright>-</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>-</ondown>
|
||||
</control>
|
||||
|
||||
<control type="group">
|
||||
<left>30</left>
|
||||
<top>140</top>
|
||||
|
||||
<!-- First Row -->
|
||||
<control type="button" id="3020">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>A</label>
|
||||
<onleft>-</onleft>
|
||||
<onright>3021</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>3026</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3021">
|
||||
<left>30</left>
|
||||
<top>0</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>B</label>
|
||||
<onleft>3020</onleft>
|
||||
<onright>3022</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>3027</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3022">
|
||||
<left>60</left>
|
||||
<top>0</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>C</label>
|
||||
<onleft>3021</onleft>
|
||||
<onright>3023</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>3028</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3023">
|
||||
<left>90</left>
|
||||
<top>0</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>D</label>
|
||||
<onleft>3022</onleft>
|
||||
<onright>3024</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>3029</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3024">
|
||||
<left>120</left>
|
||||
<top>0</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>E</label>
|
||||
<onleft>3023</onleft>
|
||||
<onright>3025</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>3030</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3025">
|
||||
<left>150</left>
|
||||
<top>0</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>F</label>
|
||||
<onleft>3024</onleft>
|
||||
<onright>3110</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>3031</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<!-- Second Row -->
|
||||
<control type="button" id="3026">
|
||||
<left>0</left>
|
||||
<top>30</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>G</label>
|
||||
<onleft>-</onleft>
|
||||
<onright>3027</onright>
|
||||
<onup>3020</onup>
|
||||
<ondown>3032</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3027">
|
||||
<left>30</left>
|
||||
<top>30</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>H</label>
|
||||
<onleft>3026</onleft>
|
||||
<onright>3028</onright>
|
||||
<onup>3021</onup>
|
||||
<ondown>3033</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3028">
|
||||
<left>60</left>
|
||||
<top>30</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>I</label>
|
||||
<onleft>3027</onleft>
|
||||
<onright>3029</onright>
|
||||
<onup>3022</onup>
|
||||
<ondown>3034</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3029">
|
||||
<left>90</left>
|
||||
<top>30</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>J</label>
|
||||
<onleft>3028</onleft>
|
||||
<onright>3030</onright>
|
||||
<onup>3023</onup>
|
||||
<ondown>3035</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3030">
|
||||
<left>120</left>
|
||||
<top>30</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>K</label>
|
||||
<onleft>3029</onleft>
|
||||
<onright>3031</onright>
|
||||
<onup>3024</onup>
|
||||
<ondown>3036</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3031">
|
||||
<left>150</left>
|
||||
<top>30</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>L</label>
|
||||
<onleft>3030</onleft>
|
||||
<onright>3110</onright>
|
||||
<onup>3025</onup>
|
||||
<ondown>3037</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<!-- Third Row -->
|
||||
<control type="button" id="3032">
|
||||
<left>0</left>
|
||||
<top>60</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>M</label>
|
||||
<onleft>-</onleft>
|
||||
<onright>3033</onright>
|
||||
<onup>3026</onup>
|
||||
<ondown>3038</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3033">
|
||||
<left>30</left>
|
||||
<top>60</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>N</label>
|
||||
<onleft>3032</onleft>
|
||||
<onright>3034</onright>
|
||||
<onup>3027</onup>
|
||||
<ondown>3039</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3034">
|
||||
<left>60</left>
|
||||
<top>60</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>O</label>
|
||||
<onleft>3033</onleft>
|
||||
<onright>3035</onright>
|
||||
<onup>3028</onup>
|
||||
<ondown>3040</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3035">
|
||||
<left>90</left>
|
||||
<top>60</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>P</label>
|
||||
<onleft>3034</onleft>
|
||||
<onright>3036</onright>
|
||||
<onup>3029</onup>
|
||||
<ondown>3041</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3036">
|
||||
<left>120</left>
|
||||
<top>60</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>Q</label>
|
||||
<onleft>3035</onleft>
|
||||
<onright>3037</onright>
|
||||
<onup>3030</onup>
|
||||
<ondown>3042</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3037">
|
||||
<left>150</left>
|
||||
<top>60</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>R</label>
|
||||
<onleft>3036</onleft>
|
||||
<onright>3110</onright>
|
||||
<onup>3031</onup>
|
||||
<ondown>3043</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<!-- Forth Row -->
|
||||
<control type="button" id="3038">
|
||||
<left>0</left>
|
||||
<top>90</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>S</label>
|
||||
<onleft>-</onleft>
|
||||
<onright>3039</onright>
|
||||
<onup>3032</onup>
|
||||
<ondown>3044</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3039">
|
||||
<left>30</left>
|
||||
<top>90</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>T</label>
|
||||
<onleft>3038</onleft>
|
||||
<onright>3040</onright>
|
||||
<onup>3033</onup>
|
||||
<ondown>3045</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3040">
|
||||
<left>60</left>
|
||||
<top>90</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>U</label>
|
||||
<onleft>3039</onleft>
|
||||
<onright>3041</onright>
|
||||
<onup>3034</onup>
|
||||
<ondown>3046</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3041">
|
||||
<left>90</left>
|
||||
<top>90</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>V</label>
|
||||
<onleft>3040</onleft>
|
||||
<onright>3042</onright>
|
||||
<onup>3035</onup>
|
||||
<ondown>3047</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3042">
|
||||
<left>120</left>
|
||||
<top>90</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>W</label>
|
||||
<onleft>3041</onleft>
|
||||
<onright>3043</onright>
|
||||
<onup>3036</onup>
|
||||
<ondown>3048</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3043">
|
||||
<left>150</left>
|
||||
<top>90</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>X</label>
|
||||
<onleft>3042</onleft>
|
||||
<onright>3110</onright>
|
||||
<onup>3037</onup>
|
||||
<ondown>3049</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<!-- Fifth Row -->
|
||||
<control type="button" id="3044">
|
||||
<left>0</left>
|
||||
<top>120</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>Y</label>
|
||||
<onleft>-</onleft>
|
||||
<onright>3045</onright>
|
||||
<onup>3038</onup>
|
||||
<ondown>3050</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3045">
|
||||
<left>30</left>
|
||||
<top>120</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>Z</label>
|
||||
<onleft>3044</onleft>
|
||||
<onright>3046</onright>
|
||||
<onup>3039</onup>
|
||||
<ondown>3051</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3046">
|
||||
<left>60</left>
|
||||
<top>120</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[0]</label>
|
||||
<onleft>3045</onleft>
|
||||
<onright>3047</onright>
|
||||
<onup>3040</onup>
|
||||
<ondown>3052</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3047">
|
||||
<left>90</left>
|
||||
<top>120</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[1]</label>
|
||||
<onleft>3046</onleft>
|
||||
<onright>3048</onright>
|
||||
<onup>3041</onup>
|
||||
<ondown>3053</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3048">
|
||||
<left>120</left>
|
||||
<top>120</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[2]</label>
|
||||
<onleft>3047</onleft>
|
||||
<onright>3049</onright>
|
||||
<onup>3042</onup>
|
||||
<ondown>3054</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3049">
|
||||
<left>150</left>
|
||||
<top>120</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[3]</label>
|
||||
<onleft>3048</onleft>
|
||||
<onright>3110</onright>
|
||||
<onup>3043</onup>
|
||||
<ondown>3055</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<!-- Sixth Row -->
|
||||
<control type="button" id="3050">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[4]</label>
|
||||
<onleft>-</onleft>
|
||||
<onright>3051</onright>
|
||||
<onup>3044</onup>
|
||||
<ondown>3056</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3051">
|
||||
<left>30</left>
|
||||
<top>150</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[5]</label>
|
||||
<onleft>3050</onleft>
|
||||
<onright>3052</onright>
|
||||
<onup>3045</onup>
|
||||
<ondown>3056</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3052">
|
||||
<left>60</left>
|
||||
<top>150</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[6]</label>
|
||||
<onleft>3051</onleft>
|
||||
<onright>3053</onright>
|
||||
<onup>3046</onup>
|
||||
<ondown>3057</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3053">
|
||||
<left>90</left>
|
||||
<top>150</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[7]</label>
|
||||
<onleft>3052</onleft>
|
||||
<onright>3054</onright>
|
||||
<onup>3047</onup>
|
||||
<ondown>3057</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3054">
|
||||
<left>120</left>
|
||||
<top>150</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[8]</label>
|
||||
<onleft>3053</onleft>
|
||||
<onright>3055</onright>
|
||||
<onup>3048</onup>
|
||||
<ondown>3058</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3055">
|
||||
<left>150</left>
|
||||
<top>150</top>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
<label>$NUMBER[9]</label>
|
||||
<onleft>3054</onleft>
|
||||
<onright>3110</onright>
|
||||
<onup>3049</onup>
|
||||
<ondown>3058</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<!-- Seventh Row -->
|
||||
<control type="button" id="3056">
|
||||
<left>0</left>
|
||||
<top>180</top>
|
||||
<width>60</width>
|
||||
<height>30</height>
|
||||
<label>DEL</label>
|
||||
<onleft>-</onleft>
|
||||
<onright>3057</onright>
|
||||
<onup>3050</onup>
|
||||
<ondown>-</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3057">
|
||||
<left>60</left>
|
||||
<top>180</top>
|
||||
<width>60</width>
|
||||
<height>30</height>
|
||||
<label>SPC</label>
|
||||
<onleft>3056</onleft>
|
||||
<onright>3058</onright>
|
||||
<onup>3052</onup>
|
||||
<ondown>-</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
<control type="button" id="3058">
|
||||
<left>120</left>
|
||||
<top>180</top>
|
||||
<width>60</width>
|
||||
<height>30</height>
|
||||
<label>CLR</label>
|
||||
<onleft>3057</onleft>
|
||||
<onright>3110</onright>
|
||||
<onup>3054</onup>
|
||||
<ondown>-</ondown>
|
||||
<align>center</align>
|
||||
<aligny>center</aligny>
|
||||
<font>font12</font>
|
||||
</control>
|
||||
|
||||
</control>
|
||||
|
||||
|
||||
<!-- Movie Results -->
|
||||
|
||||
<control type="label">
|
||||
<left>265</left>
|
||||
<top>40</top>
|
||||
<height>20</height>
|
||||
<width>190</width>
|
||||
<label>Movies</label>
|
||||
<font>font14</font>
|
||||
<textcolor>white</textcolor>
|
||||
<angle>-90</angle>
|
||||
</control>
|
||||
|
||||
<control type="fixedlist" id="3110">
|
||||
<left>280</left>
|
||||
<top>20</top>
|
||||
<width>700</width>
|
||||
<height>170</height>
|
||||
<onleft>3025</onleft>
|
||||
<onright>-</onright>
|
||||
<onup>-</onup>
|
||||
<ondown>3111</ondown>
|
||||
<pagecontrol>-</pagecontrol>
|
||||
<scrolltime>200</scrolltime>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout width="120">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-nofocus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label]</label>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout width="120">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-nofocus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-focus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
<visible>Control.HasFocus(3110)</visible>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label]</label>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
|
||||
<!-- Series -->
|
||||
|
||||
<control type="label">
|
||||
<left>265</left>
|
||||
<top>240</top>
|
||||
<height>20</height>
|
||||
<width>190</width>
|
||||
<label>Series</label>
|
||||
<font>font14</font>
|
||||
<textcolor>white</textcolor>
|
||||
<angle>-90</angle>
|
||||
</control>
|
||||
|
||||
<control type="fixedlist" id="3111">
|
||||
<left>280</left>
|
||||
<top>200</top>
|
||||
<width>700</width>
|
||||
<height>170</height>
|
||||
<onleft>3025</onleft>
|
||||
<onright>-</onright>
|
||||
<onup>3110</onup>
|
||||
<ondown>3112</ondown>
|
||||
<pagecontrol>-</pagecontrol>
|
||||
<scrolltime>200</scrolltime>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout width="120">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-nofocus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label]</label>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout width="120">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-nofocus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-focus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
<visible>Control.HasFocus(3111)</visible>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label]</label>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
|
||||
<!-- Episods -->
|
||||
|
||||
<control type="label">
|
||||
<left>265</left>
|
||||
<top>420</top>
|
||||
<height>20</height>
|
||||
<width>190</width>
|
||||
<label>Episodes</label>
|
||||
<font>font14</font>
|
||||
<textcolor>white</textcolor>
|
||||
<angle>-90</angle>
|
||||
</control>
|
||||
|
||||
<control type="fixedlist" id="3112">
|
||||
<left>280</left>
|
||||
<top>380</top>
|
||||
<width>700</width>
|
||||
<height>190</height>
|
||||
<onleft>3025</onleft>
|
||||
<onright>-</onright>
|
||||
<onup>3111</onup>
|
||||
<ondown>-</ondown>
|
||||
<pagecontrol>-</pagecontrol>
|
||||
<scrolltime>200</scrolltime>
|
||||
<orientation>horizontal</orientation>
|
||||
<itemlayout width="120">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-nofocus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label]</label>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>170</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label2]</label>
|
||||
</control>
|
||||
</itemlayout>
|
||||
<focusedlayout width="120">
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-nofocus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
</control>
|
||||
<control type="image">
|
||||
<left>0</left>
|
||||
<top>0</top>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
<texture>$INFO[Listitem.Icon]</texture>
|
||||
<bordertexture border="5">button-focus.png</bordertexture>
|
||||
<bordersize>5</bordersize>
|
||||
<visible>Control.HasFocus(3112)</visible>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>150</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label]</label>
|
||||
</control>
|
||||
<control type="label">
|
||||
<left>0</left>
|
||||
<top>170</top>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
<align>left</align>
|
||||
<font>font10</font>
|
||||
<textcolor>FFFFFFFFFF</textcolor>
|
||||
<label>$INFO[Listitem.Label2]</label>
|
||||
</control>
|
||||
</focusedlayout>
|
||||
</control>
|
||||
|
||||
|
||||
</controls>
|
||||
</window>
|
340
service.py
Normal file
340
service.py
Normal file
@ -0,0 +1,340 @@
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import urllib
|
||||
import httplib
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
import socket
|
||||
|
||||
import threading
|
||||
import json
|
||||
from datetime import datetime
|
||||
import xml.etree.ElementTree as xml
|
||||
|
||||
import mimetypes
|
||||
from threading import Thread
|
||||
from urlparse import parse_qs
|
||||
from urllib import urlretrieve
|
||||
|
||||
from random import randint
|
||||
import random
|
||||
import urllib2
|
||||
|
||||
__cwd__ = xbmcaddon.Addon(id='plugin.video.xbmb3c').getAddonInfo('path')
|
||||
__addon__ = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
__language__ = __addon__.getLocalizedString
|
||||
BASE_RESOURCE_PATH = xbmc.translatePath( os.path.join( __cwd__, 'resources', 'lib' ) )
|
||||
sys.path.append(BASE_RESOURCE_PATH)
|
||||
base_window = xbmcgui.Window( 10000 )
|
||||
|
||||
from InfoUpdater import InfoUpdaterThread
|
||||
from NextUpItems import NextUpUpdaterThread
|
||||
from SuggestedItems import SuggestedUpdaterThread
|
||||
from RandomItems import RandomInfoUpdaterThread
|
||||
from ArtworkLoader import ArtworkRotationThread
|
||||
from ThemeMusic import ThemeMusicThread
|
||||
from RecentItems import RecentInfoUpdaterThread
|
||||
from InProgressItems import InProgressUpdaterThread
|
||||
from WebSocketClient import WebSocketThread
|
||||
from ClientInformation import ClientInformation
|
||||
from MenuLoad import LoadMenuOptionsThread
|
||||
|
||||
_MODE_BASICPLAY=12
|
||||
|
||||
def getAuthHeader():
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
deviceName = addonSettings.getSetting('deviceName')
|
||||
deviceName = deviceName.replace("\"", "_") # might need to url encode this as it is getting added to the header and is user entered data
|
||||
clientInfo = ClientInformation()
|
||||
txt_mac = clientInfo.getMachineId()
|
||||
version = clientInfo.getVersion()
|
||||
userid = xbmcgui.Window( 10000 ).getProperty("userid")
|
||||
authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"XBMC\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
|
||||
headers = {'Accept-encoding': 'gzip', 'Authorization' : authString}
|
||||
xbmc.log("XBMB3C Authentication Header : " + str(headers))
|
||||
return headers
|
||||
|
||||
# start some worker threads
|
||||
|
||||
newInProgressThread = None
|
||||
if __addon__.getSetting('useInProgressUpdater') == "true":
|
||||
newInProgressThread = InProgressUpdaterThread()
|
||||
newInProgressThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service InProgressUpdater Disabled")
|
||||
|
||||
newRecentInfoThread = None
|
||||
if __addon__.getSetting('useRecentInfoUpdater') == "true":
|
||||
newRecentInfoThread = RecentInfoUpdaterThread()
|
||||
newRecentInfoThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service RecentInfoUpdater Disabled")
|
||||
|
||||
newRandomInfoThread = None
|
||||
if __addon__.getSetting('useRandomInfo') == "true":
|
||||
newRandomInfoThread = RandomInfoUpdaterThread()
|
||||
newRandomInfoThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service RandomInfo Disabled")
|
||||
|
||||
newNextUpThread = None
|
||||
if __addon__.getSetting('useNextUp') == "true":
|
||||
newNextUpThread = NextUpUpdaterThread()
|
||||
newNextUpThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service NextUp Disabled")
|
||||
|
||||
newSuggestedThread = None
|
||||
if __addon__.getSetting('useSuggested') == "true":
|
||||
newSuggestedThread = SuggestedUpdaterThread()
|
||||
newSuggestedThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service Suggested Disabled")
|
||||
|
||||
newWebSocketThread = None
|
||||
if __addon__.getSetting('useWebSocketRemote') == "true":
|
||||
newWebSocketThread = WebSocketThread()
|
||||
newWebSocketThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service WebSocketRemote Disabled")
|
||||
|
||||
newMenuThread = None
|
||||
if __addon__.getSetting('useMenuLoader') == "true":
|
||||
newMenuThread = LoadMenuOptionsThread()
|
||||
newMenuThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service MenuLoader Disabled")
|
||||
|
||||
artworkRotationThread = None
|
||||
if __addon__.getSetting('useBackgroundLoader') == "true":
|
||||
artworkRotationThread = ArtworkRotationThread()
|
||||
artworkRotationThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service BackgroundLoader Disabled")
|
||||
|
||||
newThemeMusicThread = None
|
||||
if __addon__.getSetting('useThemeMusic') == "true":
|
||||
newThemeMusicThread = ThemeMusicThread()
|
||||
newThemeMusicThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service ThemeMusic Disabled")
|
||||
|
||||
newInfoThread = None
|
||||
if __addon__.getSetting('useInfoLoader') == "true":
|
||||
newInfoThread = InfoUpdaterThread()
|
||||
newInfoThread.start()
|
||||
else:
|
||||
xbmc.log("XBMB3C Service InfoLoader Disabled")
|
||||
|
||||
def deleteItem (url):
|
||||
return_value = xbmcgui.Dialog().yesno(__language__(30091),__language__(30092))
|
||||
if return_value:
|
||||
xbmc.log('Deleting via URL: ' + url)
|
||||
progress = xbmcgui.DialogProgress()
|
||||
progress.create(__language__(30052), __language__(30053))
|
||||
resp = requests.delete(url, data='', headers=getAuthHeader())
|
||||
deleteSleep=0
|
||||
while deleteSleep<10:
|
||||
xbmc.sleep(1000)
|
||||
deleteSleep=deleteSleep+1
|
||||
progress.update(deleteSleep*10,__language__(30053))
|
||||
progress.close()
|
||||
xbmc.executebuiltin("Container.Refresh")
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def markWatched(url):
|
||||
xbmc.log('XBMB3C Service -> Marking watched via: ' + url)
|
||||
resp = requests.post(url, data='', headers=getAuthHeader())
|
||||
|
||||
def markUnWatched(url):
|
||||
xbmc.log('XBMB3C Service -> Marking watched via: ' + url)
|
||||
resp = requests.delete(url, data='', headers=getAuthHeader())
|
||||
|
||||
def setPosition (url, method):
|
||||
xbmc.log('XBMB3C Service -> Setting position via: ' + url)
|
||||
if method == 'POST':
|
||||
resp = requests.post(url, data='', headers=getAuthHeader())
|
||||
elif method == 'DELETE':
|
||||
resp = requests.delete(url, data='', headers=getAuthHeader())
|
||||
|
||||
def stopTranscoding(url):
|
||||
xbmc.log('XBMB3C Service -> Stopping transcoding: ' + url)
|
||||
resp = requests.delete(url, data='', headers=getAuthHeader())
|
||||
|
||||
|
||||
def hasData(data):
|
||||
if(data == None or len(data) == 0 or data == "None"):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def stopAll(played_information):
|
||||
|
||||
if(len(played_information) == 0):
|
||||
return
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
xbmc.log ("XBMB3C Service -> played_information : " + str(played_information))
|
||||
|
||||
for item_url in played_information:
|
||||
data = played_information.get(item_url)
|
||||
if(data != None):
|
||||
xbmc.log ("XBMB3C Service -> item_url : " + item_url)
|
||||
xbmc.log ("XBMB3C Service -> item_data : " + str(data))
|
||||
|
||||
watchedurl = data.get("watchedurl")
|
||||
positionurl = data.get("positionurl")
|
||||
deleteurl = data.get("deleteurl")
|
||||
runtime = data.get("runtime")
|
||||
currentPossition = data.get("currentPossition")
|
||||
item_id = data.get("item_id")
|
||||
|
||||
if(currentPossition != None and hasData(runtime) and hasData(positionurl) and hasData(watchedurl)):
|
||||
runtimeTicks = int(runtime)
|
||||
xbmc.log ("XBMB3C Service -> runtimeticks:" + str(runtimeTicks))
|
||||
percentComplete = (currentPossition * 10000000) / runtimeTicks
|
||||
markPlayedAt = float(addonSettings.getSetting("markPlayedAt")) / 100
|
||||
|
||||
xbmc.log ("XBMB3C Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt))
|
||||
if (percentComplete > markPlayedAt):
|
||||
|
||||
gotDeleted = 0
|
||||
if(deleteurl != None and deleteurl != ""):
|
||||
xbmc.log ("XBMB3C Service -> Offering Delete:" + str(deleteurl))
|
||||
gotDeleted = deleteItem(deleteurl)
|
||||
|
||||
if(gotDeleted == 0):
|
||||
setPosition(positionurl + '/Progress?PositionTicks=0', 'POST')
|
||||
if(newWebSocketThread != None):
|
||||
newWebSocketThread.playbackStopped(item_id, str(0))
|
||||
markWatched(watchedurl)
|
||||
else:
|
||||
#markUnWatched(watchedurl) # this resets the LastPlayedDate and that causes issues with sortby PlayedDate so I removed it for now
|
||||
if(newWebSocketThread != None):
|
||||
newWebSocketThread.playbackStopped(item_id, str(int(currentPossition * 10000000)))
|
||||
setPosition(positionurl + '?PositionTicks=' + str(int(currentPossition * 10000000)), 'DELETE')
|
||||
|
||||
if(newNextUpThread != None):
|
||||
newNextUpThread.updateNextUp()
|
||||
|
||||
if(artworkRotationThread != None):
|
||||
artworkRotationThread.updateActionUrls()
|
||||
|
||||
played_information.clear()
|
||||
|
||||
# stop transcoding - todo check we are actually transcoding?
|
||||
clientInfo = ClientInformation()
|
||||
txt_mac = clientInfo.getMachineId()
|
||||
url = ("http://%s:%s/mediabrowser/Videos/ActiveEncodings" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port')))
|
||||
url = url + '?DeviceId=' + txt_mac
|
||||
stopTranscoding(url)
|
||||
class Service( xbmc.Player ):
|
||||
|
||||
played_information = {}
|
||||
|
||||
def __init__( self, *args ):
|
||||
xbmc.log("XBMB3C Service -> starting monitor service")
|
||||
self.played_information = {}
|
||||
pass
|
||||
|
||||
def onPlayBackStarted( self ):
|
||||
# Will be called when xbmc starts playing a file
|
||||
stopAll(self.played_information)
|
||||
|
||||
currentFile = xbmc.Player().getPlayingFile()
|
||||
xbmc.log("XBMB3C Service -> onPlayBackStarted" + currentFile)
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
watchedurl = WINDOW.getProperty(currentFile+"watchedurl")
|
||||
deleteurl = WINDOW.getProperty(currentFile+"deleteurl")
|
||||
positionurl = WINDOW.getProperty(currentFile+"positionurl")
|
||||
runtime = WINDOW.getProperty(currentFile+"runtimeticks")
|
||||
item_id = WINDOW.getProperty(currentFile+"item_id")
|
||||
|
||||
# reset all these so they dont get used is xbmc plays a none
|
||||
# xbmb3c MB item
|
||||
# WINDOW.setProperty(currentFile+"watchedurl", "")
|
||||
# WINDOW.setProperty(currentFile+"deleteurl", "")
|
||||
# WINDOW.setProperty(currentFile+"positionurl", "")
|
||||
# WINDOW.setProperty(currentFile+"runtimeticks", "")
|
||||
# WINDOW.setProperty(currentFile+"item_id", "")
|
||||
|
||||
if(item_id == None or len(item_id) == 0):
|
||||
return
|
||||
|
||||
if(newWebSocketThread != None):
|
||||
newWebSocketThread.playbackStarted(item_id)
|
||||
|
||||
if (watchedurl != "" and positionurl != ""):
|
||||
|
||||
data = {}
|
||||
data["watchedurl"] = watchedurl
|
||||
data["deleteurl"] = deleteurl
|
||||
data["positionurl"] = positionurl
|
||||
data["runtime"] = runtime
|
||||
data["item_id"] = item_id
|
||||
self.played_information[currentFile] = data
|
||||
|
||||
xbmc.log("XBMB3C Service -> ADDING_FILE : " + currentFile)
|
||||
xbmc.log("XBMB3C Service -> ADDING_FILE : " + str(self.played_information))
|
||||
|
||||
# reset in progress possition
|
||||
setPosition(positionurl + '/Progress?PositionTicks=0', 'POST')
|
||||
|
||||
def onPlayBackEnded( self ):
|
||||
# Will be called when xbmc stops playing a file
|
||||
xbmc.log("XBMB3C Service -> onPlayBackEnded")
|
||||
stopAll(self.played_information)
|
||||
|
||||
def onPlayBackStopped( self ):
|
||||
# Will be called when user stops xbmc playing a file
|
||||
xbmc.log("XBMB3C Service -> onPlayBackStopped")
|
||||
stopAll(self.played_information)
|
||||
|
||||
monitor = Service()
|
||||
lastProgressUpdate = datetime.today()
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c')
|
||||
if socket.gethostname() != None and socket.gethostname() != '' and addonSettings.getSetting("deviceName") == 'XBMB3C':
|
||||
addonSettings.setSetting("deviceName", socket.gethostname())
|
||||
|
||||
while not xbmc.abortRequested:
|
||||
if xbmc.Player().isPlaying():
|
||||
try:
|
||||
|
||||
playTime = xbmc.Player().getTime()
|
||||
currentFile = xbmc.Player().getPlayingFile()
|
||||
|
||||
if(monitor.played_information.get(currentFile) != None):
|
||||
monitor.played_information[currentFile]["currentPossition"] = playTime
|
||||
|
||||
# send update
|
||||
td = datetime.today() - lastProgressUpdate
|
||||
secDiff = td.seconds
|
||||
if(secDiff > 10):
|
||||
if(monitor.played_information.get(currentFile) != None and monitor.played_information.get(currentFile).get("item_id") != None):
|
||||
item_id = monitor.played_information.get(currentFile).get("item_id")
|
||||
if(newWebSocketThread != None):
|
||||
newWebSocketThread.sendProgressUpdate(item_id, str(int(playTime * 10000000)))
|
||||
lastProgressUpdate = datetime.today()
|
||||
|
||||
except Exception, e:
|
||||
xbmc.log("XBMB3C Service -> Exception in Playback Monitor : " + str(e))
|
||||
pass
|
||||
|
||||
xbmc.sleep(1000)
|
||||
xbmcgui.Window(10000).setProperty("XBMB3C_Service_Timestamp", str(int(time.time())))
|
||||
|
||||
# stop the WebSocket client
|
||||
if(newWebSocketThread != None):
|
||||
newWebSocketThread.stopClient()
|
||||
|
||||
# stop the image proxy
|
||||
keepServing = False
|
||||
|
||||
xbmc.log("XBMB3C Service -> Service shutting down")
|
||||
|
Loading…
Reference in New Issue
Block a user