new addon based on the XBMC addon
This commit is contained in:
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")
|
||||||
|
|
||||||
Reference in New Issue
Block a user