mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-24 09:46:11 +00:00
initial commit - first version
This commit is contained in:
parent
fdbfc9455b
commit
860bdfbbd8
24 changed files with 3353 additions and 0 deletions
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
|
||||
-------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------
|
25
addon.xml
Normal file
25
addon.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.mb3sync"
|
||||
name="MediaBrowser Syncer"
|
||||
version="0.0.1"
|
||||
provider-name="mediabrowser.tv">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource"
|
||||
library="default.py">
|
||||
<provides>executable video audio image</provides>
|
||||
</extension>
|
||||
<extension point="xbmc.service" library="service.py" start="login">
|
||||
</extension>
|
||||
<extension point="xbmc.addon.metadata">
|
||||
<platform>all</platform>
|
||||
<language>en</language>
|
||||
<license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license>
|
||||
<forum></forum>
|
||||
<website>http://mediabrowser.tv/</website>
|
||||
<source></source>
|
||||
<summary lang="en"></summary>
|
||||
<description lang="en"></description>
|
||||
</extension>
|
||||
</addon>
|
2
changelog.txt
Normal file
2
changelog.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
0.0.1
|
||||
- initital alpha version
|
27
default.py
Normal file
27
default.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import xbmcaddon
|
||||
import xbmcplugin
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import os
|
||||
import threading
|
||||
import json
|
||||
import urllib
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
cwd = addonSettings.getAddonInfo('path')
|
||||
BASE_RESOURCE_PATH = xbmc.translatePath( os.path.join( cwd, 'resources', 'lib' ) )
|
||||
sys.path.append(BASE_RESOURCE_PATH)
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
import Utils as utils
|
||||
from PlaybackUtils import PlaybackUtils
|
||||
|
||||
# get the actions...
|
||||
params=utils.get_params(sys.argv[2])
|
||||
mode = params.get('mode',"")
|
||||
id = params.get('id',"")
|
||||
|
||||
if mode == "play":
|
||||
PlaybackUtils().PLAY(id)
|
||||
|
BIN
fanart.jpg
Normal file
BIN
fanart.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 386 KiB |
BIN
icon.png
Normal file
BIN
icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
1
resources/__init__.py
Normal file
1
resources/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# Dummy file to make this directory a package.
|
239
resources/language/Dutch/strings.xml
Normal file
239
resources/language/Dutch/strings.xml
Normal file
|
@ -0,0 +1,239 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<strings>
|
||||
<string id="30000">Primaire server adres:</string>
|
||||
<string id="30001">Enkele mappen automatisch openen:</string>
|
||||
<string id="30002">Afspelen van stream ipv SMB:</string>
|
||||
<string id="30004">Log niveau:</string>
|
||||
<string id="30005">Gebruikersnaam: </string>
|
||||
<string id="30006">Wachtwoord: </string>
|
||||
<string id="30007">Samba gebruikersnaam: </string>
|
||||
<string id="30008">Samba wachtwoord: </string>
|
||||
<string id="30009">Transcode: </string>
|
||||
<string id="30010">Prestatie profilering inschakelen:</string>
|
||||
<string id="30011">Lokale cache systeem</string>
|
||||
|
||||
<string id="30014">MediaBrowser</string>
|
||||
<string id="30015">Netwerk</string>
|
||||
<string id="30016">Apparaatnaam</string>
|
||||
|
||||
<string id="30022">Geavanceerd</string>
|
||||
<string id="30024">Gebruikersnaam:</string>
|
||||
<string id="30025">Wachtwoord:</string>
|
||||
<string id="30026">Gebruik SIMPLEJSON ipv JSON</string>
|
||||
|
||||
<string id="30030">Poortnummer:</string>
|
||||
<string id="30036">Aantal recente films die getoond worden:</string>
|
||||
<string id="30037">Aantal recente TV-series die getoond worden:</string>
|
||||
<string id="30035">Aantal recente muziekalbums die getoond worden:</string>
|
||||
<string id="30038">Markeer als bekeken bij starten van afspelen:</string>
|
||||
<string id="30039">Gebruik seizoen poster bij afleveringen</string>
|
||||
|
||||
<string id="30040">Genre filter ...</string>
|
||||
<string id="30041">Speel alles vanaf hier</string>
|
||||
<string id="30042">Vernieuwen</string>
|
||||
<string id="30043">Wissen</string>
|
||||
<string id="30046">Voeg film toe aan CouchPotato</string>
|
||||
|
||||
<string id="30044">Ongeldige gebruikersnaam/wachtwoord</string>
|
||||
<string id="30045">Gebruikersnaam niet gevonden</string>
|
||||
|
||||
<string id="30052">Wissen...</string>
|
||||
<string id="30053">Wacht op server voor wissen</string>
|
||||
|
||||
<string id="30059">Server standaard</string>
|
||||
<string id="30060">Titel</string>
|
||||
<string id="30061">Jaar</string>
|
||||
<string id="30062">Premiere datum</string>
|
||||
<string id="30063">Datum toegevoegd</string>
|
||||
<string id="30064">Critici beoordeling</string>
|
||||
<string id="30065">Community beoordeling</string>
|
||||
<string id="30066">Aantal keer bekeken</string>
|
||||
<string id="30067">Budget</string>
|
||||
|
||||
<string id="30068">Sorteer op</string>
|
||||
|
||||
<string id="30069">Geen</string>
|
||||
<string id="30070">Actie</string>
|
||||
<string id="30071">Avontuur</string>
|
||||
<string id="30072">Animatie</string>
|
||||
<string id="30073">Misdaad</string>
|
||||
<string id="30074">Comedy</string>
|
||||
<string id="30075">Documentaire</string>
|
||||
<string id="30076">Drama</string>
|
||||
<string id="30077">Fantasie</string>
|
||||
<string id="30078">Nederlands</string>
|
||||
<string id="30079">Historie</string>
|
||||
<string id="30080">Horror</string>
|
||||
<string id="30081">Muziek</string>
|
||||
<string id="30082">Musical</string>
|
||||
<string id="30083">Mysterie</string>
|
||||
<string id="30084">Romantiek</string>
|
||||
<string id="30085">Science Fiction</string>
|
||||
<string id="30086">Kort</string>
|
||||
<string id="30087">Spanning</string>
|
||||
<string id="30088">Thriller</string>
|
||||
<string id="30089">Western</string>
|
||||
|
||||
<string id="30090">Genre filter</string>
|
||||
<string id="30091">Bevestig wissen</string>
|
||||
<string id="30092">Dit item wissen ? Dit zal het bestand volledig verwijderen.</string>
|
||||
|
||||
<string id="30093">Markeer als bekeken</string>
|
||||
<string id="30094">Mark als onbekeken</string>
|
||||
<string id="30095">Voeg toe aan favorieten</string>
|
||||
<string id="30096">Verwijder uit favorieten</string>
|
||||
<string id="30097">Sorteer op...</string>
|
||||
<string id="30098">Sorteer oplopend</string>
|
||||
<string id="30099">Sorteer aflopend</string>
|
||||
<string id="30100">Toon acteurs</string>
|
||||
|
||||
<!-- resume dialog -->
|
||||
<string id="30105">Hervatten</string>
|
||||
<string id="30106">Hervatten vanaf</string>
|
||||
<string id="30107">Start vanaf begin</string>
|
||||
|
||||
<string id="30110">Interface</string>
|
||||
<string id="30111">Inclusief stream info</string>
|
||||
<string id="30112">Inclusief personen</string>
|
||||
<string id="30113">Inclusief filminfo</string>
|
||||
<string id="30114">Bij hervatten aantal seconden terugspringen</string>
|
||||
<string id="30115">Markeer als bekeken wanneer na percentage gestopt</string>
|
||||
<string id="30116">Inclusief aantal en afspeel tellers</string>
|
||||
<string id="30117"> - Achtergrond plaatjes verversen (secondes)</string>
|
||||
<string id="30118">Inclusief hervat-percentage</string>
|
||||
<string id="30119">Afleveringnummer tonen in titel</string>
|
||||
<string id="30120">Toon voortgang</string>
|
||||
<string id="30121">Laden van 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">Toon wismogelijkheid na bekijken van aflevering</string>
|
||||
<string id="30128">Afspeelfout!</string>
|
||||
<string id="30129">Dit item kan niet worden afgespeeld</string>
|
||||
<string id="30130">Lockaal pad gedetecteerd</string>
|
||||
<string id="30131">De MB3 bibliotheek bevat lokale paden. U moet UNC-paden gebruiken of afspelen van stream inschakelen. Pad: </string>
|
||||
<string id="30132">Waarschuwing</string>
|
||||
<string id="30133">Debug logging ingeschakeld.</string>
|
||||
<string id="30134">Dit heeft effect op de performance.</string>
|
||||
<string id="30135">Fout</string>
|
||||
<string id="30136">XBMB3C service werkt niet</string>
|
||||
<string id="30137">Herstart XBMC aub</string>
|
||||
<string id="30138">Zoeken</string>
|
||||
|
||||
<string id="30139">Schakel Themamuziek in (vereist herstart)</string>
|
||||
<string id="30140"> - Herhalen van themamuziek</string>
|
||||
<string id="30141">Activeer achtergrondafbeelding (vereist herstart)</string>
|
||||
<string id="30142">Services</string>
|
||||
<string id="30143">Activeer Info Loader (vereist herstart)</string>
|
||||
<string id="30144">Activeer Menu Loader (vereist herstart)</string>
|
||||
<string id="30145">Activeer WebSocket Remote (vereist herstart)</string>
|
||||
<string id="30146">Activeer In Progress Loader (vereist herstart)</string>
|
||||
<string id="30147">Activeer Recent Info Loader (vereist herstart)</string>
|
||||
<string id="30148">Activeer Random Loader (vereist herstart)</string>
|
||||
<string id="30149">Activeer Next Up Loader (vereist herstart)</string>
|
||||
|
||||
<string id="30150">Skin ondersteund het vastleggen van views niet</string>
|
||||
<string id="30151">Selecteer item actie (vereist herstart)</string>
|
||||
|
||||
<string id="30152">Toon indactors</string>
|
||||
<string id="30153"> - Toon bekeken indator</string>
|
||||
<string id="30154"> - Toon aantal onbekeken indicator</string>
|
||||
<string id="30155"> - Toon afspeel-percentage indicator</string>
|
||||
<string id="30156">Sorteer volgende (NextUp) op titel</string>
|
||||
<string id="30157">Deactiveer speciale afbeeldingen (bv CoverArt)</string>
|
||||
<string id="30158">Metadata</string>
|
||||
<string id="30159">Afbeeldingen</string>
|
||||
<string id="30160">Video kwaliteit</string>
|
||||
|
||||
<string id="30161">Activeer Suggested Loader (vereist herstart)</string>
|
||||
<string id="30162">Seizoen tonen in titel</string>
|
||||
<string id="30163">Seizoenen verbergen</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">Alle films</string>
|
||||
<string id="30171">Alle TV</string>
|
||||
<string id="30172">Alle Muziek</string>
|
||||
<string id="30173">Kanalen</string>
|
||||
<string id="30174">Recent toegevoegde films</string>
|
||||
<string id="30175">Recent toegevoegde afleveringen</string>
|
||||
<string id="30176">Recent toegevoegde albums</string>
|
||||
<string id="30177">Niet afgekeken films</string>
|
||||
<string id="30178">Niet afgekeken afleveringen</string>
|
||||
<string id="30179">NextUp afleveringen</string>
|
||||
<string id="30180">Favoriete films</string>
|
||||
<string id="30181">Favoriete TV-series</string>
|
||||
<string id="30182">Favoriete afleveringen</string>
|
||||
<string id="30183">Vaak afgespeelde albums</string>
|
||||
<string id="30184">Upcoming TV</string>
|
||||
<string id="30185">BoxSets</string>
|
||||
<string id="30186">Trailers</string>
|
||||
<string id="30187">Muziek videos</string>
|
||||
<string id="30188">Fotos</string>
|
||||
<string id="30189">Onbekeken films</string>
|
||||
<string id="30190">Film Genres</string>
|
||||
<string id="30191">Film Studios</string>
|
||||
<string id="30192">Film Acteurs</string>
|
||||
<string id="30193">Onbekeken afleveringen</string>
|
||||
<string id="30194">TV Genres</string>
|
||||
<string id="30195">TV Networks</string>
|
||||
<string id="30196">TV Acteurs</string>
|
||||
<string id="30197">Afspeellijsten</string>
|
||||
<string id="30198">Zoeken</string>
|
||||
<string id="30199">Views instellen</string>
|
||||
|
||||
<string id="30200">Selecteer gebruiker</string>
|
||||
<string id="30201">Profilering ingeschakeld.</string>
|
||||
<string id="30202">Svp onthouden om weer uit te schakelen na het testen.</string>
|
||||
<string id="30203">Error in ArtworkRotationThread</string>
|
||||
<string id="30204">Kan niet verbinden met server</string>
|
||||
<string id="30205">Error in LoadMenuOptionsThread</string>
|
||||
|
||||
<string id="30206">Activeer Playlists Loader (vereist herstart)</string>
|
||||
|
||||
<string id="30207">Liedjes</string>
|
||||
<string id="30208">Albums</string>
|
||||
<string id="30209">Album artiesten</string>
|
||||
<string id="30210">Artiesten</string>
|
||||
<string id="30211">Muziek Genres</string>
|
||||
|
||||
<string id="30212">Schakel Themavideos in (vereist herstart)</string>
|
||||
<string id="30213"> - Herhalen van themavideos</string>
|
||||
|
||||
<string id="30214">Schakel het forceren van view uit</string>
|
||||
<string id="30215">Schakel snelle modus in (beta)</string>
|
||||
<string id="30216">Automatisch resterende afleveringen in een seizoen afspelen</string>
|
||||
<string id="30217">Boxsets tonen in de overzichten (vereist herstart)</string>
|
||||
<string id="30218">Afbeeldingen comprimeren</string>
|
||||
<string id="30219">Activeer Skin Helper (vereust herstart)</string>
|
||||
<string id="30220">Laatste </string>
|
||||
<string id="30221">Bezig </string>
|
||||
<string id="30222">Volgende </string>
|
||||
<string id="30223">Gebruikerweergaven </string>
|
||||
|
||||
<!-- Default views -->
|
||||
<string id="30300">Actief</string>
|
||||
<string id="30301">Herstel standaard</string>
|
||||
<string id="30302">Films</string>
|
||||
<string id="30303">BoxSets</string>
|
||||
<string id="30304">Trailers</string>
|
||||
<string id="30305">Series</string>
|
||||
<string id="30306">Seizoenen</string>
|
||||
<string id="30307">Afleveringen</string>
|
||||
<string id="30308">Muziek - artiesten</string>
|
||||
<string id="30309">Muziek - albums</string>
|
||||
<string id="30310">Muziekvideos</string>
|
||||
<string id="30311">Muziek - liedjes</string>
|
||||
<string id="30312">Kanalen</string>
|
||||
|
||||
|
||||
|
||||
</strings>
|
252
resources/language/English/strings.xml
Normal file
252
resources/language/English/strings.xml
Normal file
|
@ -0,0 +1,252 @@
|
|||
<?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">Network Username: </string>
|
||||
<string id="30008">Network Password: </string>
|
||||
<string id="30009">Transcode: </string>
|
||||
<string id="30010">Enable Performance Profiling</string>
|
||||
<string id="30011">Local caching system</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>
|
||||
<!-- Runtime added as 30226 below -->
|
||||
|
||||
<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>
|
||||
|
||||
<!-- resume dialog -->
|
||||
<string id="30105">Resume</string>
|
||||
<string id="30106">Resume from</string>
|
||||
<string id="30107">Start from beginning</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"> - Offer delete when stopped 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">Monitoring service is not running</string>
|
||||
<string id="30137">If you have just installed please restart Kodi</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 server</string>
|
||||
<string id="30205">Error in LoadMenuOptionsThread</string>
|
||||
|
||||
<string id="30206">Enable Playlists Loader (Requires Restart)</string>
|
||||
|
||||
<string id="30207">Songs</string>
|
||||
<string id="30208">Albums</string>
|
||||
<string id="30209">Album Artists</string>
|
||||
<string id="30210">Artists</string>
|
||||
<string id="30211">Music Genres</string>
|
||||
|
||||
<string id="30212">Enable Theme Videos (Requires Restart)</string>
|
||||
<string id="30213"> - Loop Theme Videos</string>
|
||||
|
||||
<string id="30214">Disable forced view</string>
|
||||
<string id="30215">Enable Fast Processing</string>
|
||||
<string id="30216">AutoPlay remaining episodes in a season</string>
|
||||
<string id="30217">Show boxsets collapsed in views (Requires Restart)</string>
|
||||
<string id="30218">Compress Artwork</string>
|
||||
<string id="30219">Enable Skin Helper (Requires Restart)</string>
|
||||
<string id="30220">Latest </string>
|
||||
<string id="30221">In Progress </string>
|
||||
<string id="30222">NextUp </string>
|
||||
<string id="30223">User Views</string>
|
||||
<string id="30224">Report Metrics</string>
|
||||
<string id="30225">Use Kodi Sorting</string>
|
||||
<string id="30226">Runtime</string>
|
||||
|
||||
<string id="30227">Random Movies</string>
|
||||
<string id="30228">Random Episodes</string>
|
||||
|
||||
<string id="30229">Skin Compatibility Warning</string>
|
||||
<string id="30230">Your current skin is not fully compatible.</string>
|
||||
<string id="30231">For a better experience use a skin from the forum.</string>
|
||||
<string id="30232">http://tinyurl.com/knfus2x</string>
|
||||
<string id="30233">Don't Show Skin Compatibility Message</string>
|
||||
<string id="30234">Add Show Name (Season + Episode)</string>
|
||||
|
||||
<!-- Default views -->
|
||||
<string id="30300">Active</string>
|
||||
<string id="30301">Clear Settings</string>
|
||||
<string id="30302">Movies</string>
|
||||
<string id="30303">BoxSets</string>
|
||||
<string id="30304">Trailers</string>
|
||||
<string id="30305">Series</string>
|
||||
<string id="30306">Seasons</string>
|
||||
<string id="30307">Episodes</string>
|
||||
<string id="30308">Music Artists</string>
|
||||
<string id="30309">Music Albums</string>
|
||||
<string id="30310">Music Videos</string>
|
||||
<string id="30311">Music Tracks</string>
|
||||
<string id="30312">Channels</string>
|
||||
|
||||
|
||||
</strings>
|
227
resources/language/German/strings.xml
Normal file
227
resources/language/German/strings.xml
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<strings>
|
||||
<string id="30000">IP-Adresse des Servers</string>
|
||||
<string id="30001">Automatisches Öffnen von Ordnern mit einem Eintrag</string>
|
||||
<string id="30002">Via HTTP abspielen statt SMB/NFS:</string>
|
||||
<string id="30004">Log Level:</string>
|
||||
<string id="30005">Benutzername: </string>
|
||||
<string id="30006">Passwort: </string>
|
||||
<string id="30007">Samba-Benutzername: </string>
|
||||
<string id="30008">Samba-Passwort: </string>
|
||||
<string id="30009">Transkodieren: </string>
|
||||
<string id="30010">Performancemessung aktivieren</string>
|
||||
<string id="30011">Caching-Mechanismus</string>
|
||||
|
||||
<string id="30014">MediaBrowser</string>
|
||||
<string id="30015">Netzwerk</string>
|
||||
<string id="30016">Gerätename</string>
|
||||
|
||||
<string id="30022">Erweitert</string>
|
||||
<string id="30024">Benutzername:</string>
|
||||
<string id="30025">Passwort:</string>
|
||||
|
||||
<string id="30030">Portnummer:</string>
|
||||
<string id="30036">Anzahl der zuletzt hinzugefügten Filme:</string>
|
||||
<string id="30037">Anzahl der zuletzt hinzugefügten Episoden:</string>
|
||||
<string id="30035">Anzahl der zuletzt hinzugefügten Alben:</string>
|
||||
<string id="30038">Bei Start der Wiedergabe als 'gesehen' markieren:</string>
|
||||
<string id="30039">Staffelposter für Episoden nutzen</string>
|
||||
|
||||
<string id="30040">Genre Filter ...</string>
|
||||
<string id="30041">Alles von hier abspielen</string>
|
||||
<string id="30042">Aktualisieren</string>
|
||||
<string id="30043">Löschen</string>
|
||||
<string id="30046">Film zu CouchPotato hinzufügen</string>
|
||||
|
||||
<string id="30044">Benutzername/Passwort falsch</string>
|
||||
<string id="30045">Benutzername nicht gefunden</string>
|
||||
|
||||
<string id="30052">Lösche</string>
|
||||
<string id="30053">Lösche von Server</string>
|
||||
|
||||
<string id="30059">Server Standard</string>
|
||||
<string id="30060">Titel</string>
|
||||
<string id="30061">Jahr</string>
|
||||
<string id="30062">Premierendatum</string>
|
||||
<string id="30063">Datum hinzugefügt</string>
|
||||
<string id="30064">Bewertung</string>
|
||||
<string id="30065">Zuschauerbewertung</string>
|
||||
<string id="30066">Abspielzähler</string>
|
||||
<string id="30067">Budget</string>
|
||||
|
||||
<string id="30068">Sortiere nach</string>
|
||||
|
||||
<string id="30069">Kein Filter</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">Löschen von Dateien bestätigen?</string>
|
||||
<string id="30092">Diesen Eintrag löschen? Diese Aktion löscht die Mediendatei und damit verbundene Daten.</string>
|
||||
|
||||
<string id="30093">Als 'gesehen' markieren</string>
|
||||
<string id="30094">Als 'ungesehen' markieren</string>
|
||||
<string id="30095">Zu Favoriten hinzufügen</string>
|
||||
<string id="30096">Von Favoriten entfernen</string>
|
||||
<string id="30097">Sortiere nach ...</string>
|
||||
<string id="30098">Sortierreihenfolge absteigend</string>
|
||||
<string id="30099">Sortierreihenfolge aufsteigend</string>
|
||||
<string id="30100">Zeige Mitwirkende</string>
|
||||
|
||||
<!-- resume dialog -->
|
||||
<string id="30105">Fortsetzen</string>
|
||||
<string id="30106">Fortsetzen bei</string>
|
||||
<string id="30107">Am Anfang starten</string>
|
||||
|
||||
<string id="30110">Benutzeroberfläche</string>
|
||||
<string id="30111">Lade Streaminformationen</string>
|
||||
<string id="30112">Lade Darsteller</string>
|
||||
<string id="30113">Lade Inhaltsübersicht</string>
|
||||
<string id="30114">Rücksprung bei Fortsetzen</string>
|
||||
<string id="30115">Bei Stop nach x % als 'gesehen' markieren</string>
|
||||
<string id="30116">Medien- und 'Abgespielt'-Zähler hinzufügen</string>
|
||||
<string id="30117"> - Aktualisierungsintervall von Hintergrundbildern (Sekunden)</string>
|
||||
<string id="30118">Prozentanzeige für Fortsetzen</string>
|
||||
<string id="30119">Episodennummer hinzufügen</string>
|
||||
<string id="30120">Ladefortschritt anzeigen</string>
|
||||
<string id="30121">Lade Inhalt</string>
|
||||
<string id="30122">Lade Daten</string>
|
||||
<string id="30123">Verarbeite Json Daten</string>
|
||||
<string id="30124">Lade Json Daten</string>
|
||||
<string id="30125">Fertig</string>
|
||||
<string id="30126">Verarbeite Eintrag : </string>
|
||||
<string id="30127">Löschen von gesehenen Episoden anbieten</string>
|
||||
<string id="30128">Abspielfehler</string>
|
||||
<string id="30129">Dieser Eintrag ist nicht abspielbar</string>
|
||||
<string id="30130">Lokaler Pfad erkannt</string>
|
||||
<string id="30132">Warnung</string>
|
||||
<string id="30133">Debug Logging aktiviert.</string>
|
||||
<string id="30134">Dies beeinträchtigt die Performance.</string>
|
||||
<string id="30135">Fehler</string>
|
||||
<string id="30136">XBMB3C-Service läuft nicht</string>
|
||||
<string id="30137">Bitte XBMC neustarten</string>
|
||||
<string id="30138">Suche</string>
|
||||
|
||||
<string id="30139">Themen-Musik aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30140"> - Themen-Musik in Schleife abspielen</string>
|
||||
<string id="30141">Laden im Hintergrund aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30142">Dienste</string>
|
||||
<string id="30143">Info-Loader aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30144">Menü-Loader aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30145">WebSocket Fernbedienung aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30146">'Laufende Medien'-Loader aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30147">'Zuletzt hinzugefügt' Loader aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30148">'Zufallsmedien'-Loader aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30149">'Nächste'-Loader aktivieren (Erfordert Neustart)</string>
|
||||
|
||||
<string id="30150">Skin unterstützt das Setzen von Views nicht</string>
|
||||
<string id="30151">Aktion bei Auswahl (Erfordert Neustart)</string>
|
||||
|
||||
<string id="30152">Indikatoren</string>
|
||||
<string id="30153"> - 'Gesehen'-Indikator anzeigen</string>
|
||||
<string id="30154"> - Zähler für ungesehene Medien anzeigen</string>
|
||||
<string id="30155"> - Abspiel-Prozentanzeige aktivieren</string>
|
||||
<string id="30156">Sortiere 'Nächste' nach Serientitel</string>
|
||||
<string id="30157">Deaktiviere erweiterte Bilder (z.B. CoverArt)</string>
|
||||
<string id="30158">Metadaten</string>
|
||||
<string id="30159">Grafiken</string>
|
||||
<string id="30160">Videoqualität</string>
|
||||
|
||||
<string id="30161">'Empfohlen'-Loader aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30162">Staffelnummer hinzufügen</string>
|
||||
<string id="30163">Serienstaffeln reduzieren</string>
|
||||
<string id="30164">Direkte Wiedergabe - HTTP</string>
|
||||
<string id="30165">Direkte Wiedergabe</string>
|
||||
<string id="30166">Transkodierung</string>
|
||||
<string id="30167">Serversuche erfolgreich</string>
|
||||
<string id="30168">Server gefunden</string>
|
||||
<string id="30169">Addresse : </string>
|
||||
|
||||
<string id="30170">Alle Filme</string>
|
||||
<string id="30171">Alle Serien</string>
|
||||
<string id="30172">Alles an Musik</string>
|
||||
<string id="30173">Kanäle</string>
|
||||
<string id="30174">Zuletzt hinzugefügte Filme</string>
|
||||
<string id="30175">Zuletzt hinzugefügte Episoden</string>
|
||||
<string id="30176">Zuletzt hinzugefügte Alben</string>
|
||||
<string id="30177">Begonnene Filme</string>
|
||||
<string id="30178">Begonnene Episoden</string>
|
||||
<string id="30179">Nächste Episoden</string>
|
||||
<string id="30180">Favorisierte Filme</string>
|
||||
<string id="30181">Favorisierte Serien</string>
|
||||
<string id="30182">Favorisierte Episoden</string>
|
||||
<string id="30183">Häufig gespielte Alben</string>
|
||||
<string id="30184">Anstehende Serien</string>
|
||||
<string id="30185">Sammlungen</string>
|
||||
<string id="30186">Trailer</string>
|
||||
<string id="30187">Musikvideos</string>
|
||||
<string id="30188">Fotos</string>
|
||||
<string id="30189">Ungesehene Filme</string>
|
||||
<string id="30190">Filmgenres</string>
|
||||
<string id="30191">Studios</string>
|
||||
<string id="30192">Filmdarsteller</string>
|
||||
<string id="30193">Ungesehene Episoden</string>
|
||||
<string id="30194">Seriengenres</string>
|
||||
<string id="30195">Fernsehsender</string>
|
||||
<string id="30196">Seriendarsteller</string>
|
||||
<string id="30197">Wiedergabelisten</string>
|
||||
<string id="30198">Suche</string>
|
||||
<string id="30199">Ansichten festlegen</string>
|
||||
|
||||
<string id="30200">Wähle Benutzer</string>
|
||||
<string id="30201">Messung aktiviert.</string>
|
||||
<string id="30202">Bitte daran denken, nach dem Testen wieder zu deaktivieren.</string>
|
||||
<string id="30203">Fehler in ArtworkRotationThread</string>
|
||||
<string id="30204">Verbindung zum Server fehlgeschlagen</string>
|
||||
<string id="30205">Fehler in LoadMenuOptionsThread</string>
|
||||
|
||||
<string id="30206">'Playlist'-Loader aktivieren (Erfordert Neustart)</string>
|
||||
|
||||
<string id="30207">Songs</string>
|
||||
<string id="30208">Alben</string>
|
||||
<string id="30209">Album-Interpreten</string>
|
||||
<string id="30210">Interpreten</string>
|
||||
<string id="30211">Musik-Genres</string>
|
||||
|
||||
<string id="30212">Themen-Videos aktivieren (Erfordert Neustart)</string>
|
||||
<string id="30213"> - Themen-Videos in Schleife abspielen</string>
|
||||
|
||||
<string id="30214">Festgelegte Ansichten deaktivieren</string>
|
||||
<string id="30215">Schnelleres Laden der Daten aktivieren</string>
|
||||
<string id="30216">Spiele weitere Episoden einer Staffel automatisch ab</string>
|
||||
<string id="30217">Aktiviere gruppierte Darstellung von Sammlungen (Erfordert Neustart)</string>
|
||||
<string id="30218">Bilder komprimieren</string>
|
||||
|
||||
<!-- Default views -->
|
||||
<string id="30300">Aktiviert</string>
|
||||
<string id="30301">Zurücksetzen</string>
|
||||
<string id="30302">Filme</string>
|
||||
<string id="30303">BoxSets</string>
|
||||
<string id="30304">Trailer</string>
|
||||
<string id="30305">Serien</string>
|
||||
<string id="30306">Staffeln</string>
|
||||
<string id="30307">Episoden</string>
|
||||
<string id="30308">Interpreten</string>
|
||||
<string id="30309">Alben</string>
|
||||
<string id="30310">Musikvideos</string>
|
||||
<string id="30311">Musikstücke</string>
|
||||
|
||||
</strings>
|
250
resources/lib/API.py
Normal file
250
resources/lib/API.py
Normal file
|
@ -0,0 +1,250 @@
|
|||
# API.py
|
||||
# This class helps translate more complex cases from the MediaBrowser API to the XBMC API
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
class API():
|
||||
|
||||
def getPeople(self, item):
|
||||
# Process People
|
||||
director=''
|
||||
writer=''
|
||||
cast=[]
|
||||
people = item.get("People")
|
||||
if(people != None):
|
||||
for person in people:
|
||||
if(person.get("Type") == "Director"):
|
||||
director = director + person.get("Name") + ' '
|
||||
if(person.get("Type") == "Writing"):
|
||||
writer = person.get("Name")
|
||||
if(person.get("Type") == "Writer"):
|
||||
writer = person.get("Name")
|
||||
if(person.get("Type") == "Actor"):
|
||||
Name = person.get("Name")
|
||||
Role = person.get("Role")
|
||||
if Role == None:
|
||||
Role = ''
|
||||
cast.append(Name)
|
||||
return {'Director' : director,
|
||||
'Writer' : writer,
|
||||
'Cast' : cast
|
||||
}
|
||||
|
||||
def getTimeInfo(self, item):
|
||||
resumeTime = ''
|
||||
userData = item.get("UserData")
|
||||
PlaybackPositionTicks = '100'
|
||||
if userData.get("PlaybackPositionTicks") != None:
|
||||
PlaybackPositionTicks = str(userData.get("PlaybackPositionTicks"))
|
||||
reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
|
||||
resumeTime = reasonableTicks / 10000
|
||||
|
||||
try:
|
||||
tempDuration = str(int(item.get("RunTimeTicks", "0"))/(10000000*60))
|
||||
except TypeError:
|
||||
try:
|
||||
tempDuration = str(int(item.get("CumulativeRunTimeTicks"))/(10000000*60))
|
||||
except TypeError:
|
||||
tempDuration = "0"
|
||||
cappedPercentage = None
|
||||
resume=0
|
||||
percentage=0
|
||||
if (resumeTime != "" and int(resumeTime) > 0):
|
||||
duration = float(tempDuration)
|
||||
if(duration > 0):
|
||||
resume = float(resumeTime) / 60
|
||||
percentage = int((resume / duration) * 100.0)
|
||||
return {'Duration' : tempDuration,
|
||||
'TotalTime' : tempDuration,
|
||||
'Percent' : str(percentage),
|
||||
'ResumeTime' : str(resume)
|
||||
}
|
||||
|
||||
def getStudio(self, item):
|
||||
# Process Studio
|
||||
studio = ""
|
||||
if item.get("SeriesStudio") != None and item.get("SeriesStudio") != '':
|
||||
studio = item.get("SeriesStudio")
|
||||
if studio == "":
|
||||
studios = item.get("Studios")
|
||||
if(studios != None):
|
||||
for studio_string in studios:
|
||||
if studio=="": #Just take the first one
|
||||
temp=studio_string.get("Name")
|
||||
studio=temp.encode('utf-8')
|
||||
return studio
|
||||
|
||||
def getMediaStreams(self, item, mediaSources=False):
|
||||
# Process MediaStreams
|
||||
channels = ''
|
||||
videocodec = ''
|
||||
audiocodec = ''
|
||||
height = ''
|
||||
width = ''
|
||||
aspectratio = '1:1'
|
||||
aspectfloat = 1.85
|
||||
|
||||
if mediaSources == True:
|
||||
mediaSources = item.get("MediaSources")
|
||||
if(mediaSources != None):
|
||||
MediaStreams = mediaSources[0].get("MediaStreams")
|
||||
else:
|
||||
MediaStreams = None
|
||||
else:
|
||||
MediaStreams = item.get("MediaStreams")
|
||||
if(MediaStreams != None):
|
||||
#mediaStreams = MediaStreams[0].get("MediaStreams")
|
||||
if(MediaStreams != None):
|
||||
for mediaStream in MediaStreams:
|
||||
if(mediaStream.get("Type") == "Video"):
|
||||
videocodec = mediaStream.get("Codec")
|
||||
height = str(mediaStream.get("Height"))
|
||||
width = str(mediaStream.get("Width"))
|
||||
aspectratio = mediaStream.get("AspectRatio")
|
||||
if aspectratio != None and len(aspectratio) >= 3:
|
||||
try:
|
||||
aspectwidth,aspectheight = aspectratio.split(':')
|
||||
aspectfloat = float(aspectwidth) / float(aspectheight)
|
||||
except:
|
||||
aspectfloat = 1.85
|
||||
if(mediaStream.get("Type") == "Audio"):
|
||||
audiocodec = mediaStream.get("Codec")
|
||||
channels = mediaStream.get("Channels")
|
||||
return {'channels' : str(channels),
|
||||
'videocodec' : videocodec,
|
||||
'audiocodec' : audiocodec,
|
||||
'height' : height,
|
||||
'width' : width,
|
||||
'aspectratio' : str(aspectfloat)
|
||||
}
|
||||
|
||||
def getUserData(self, item):
|
||||
userData = item.get("UserData")
|
||||
resumeTime = 0
|
||||
if(userData != None):
|
||||
if userData.get("Played") != True:
|
||||
watched="True"
|
||||
else:
|
||||
watched="False"
|
||||
if userData.get("IsFavorite") == True:
|
||||
favorite="True"
|
||||
else:
|
||||
favorite="False"
|
||||
if(userData.get("Played") == True):
|
||||
playcount="1"
|
||||
else:
|
||||
playcount="0"
|
||||
if userData.get('UnplayedItemCount') != None:
|
||||
UnplayedItemCount = userData.get('UnplayedItemCount')
|
||||
else:
|
||||
UnplayedItemCount = "0"
|
||||
if userData.get('PlaybackPositionTicks') != None:
|
||||
PlaybackPositionTicks = userData.get('PlaybackPositionTicks')
|
||||
else:
|
||||
PlaybackPositionTicks = ''
|
||||
return {'Watched' : watched,
|
||||
'Favorite' : favorite,
|
||||
'PlayCount': playcount,
|
||||
'UnplayedItemCount' : UnplayedItemCount,
|
||||
'PlaybackPositionTicks' : str(PlaybackPositionTicks)
|
||||
}
|
||||
|
||||
def getGenre(self,item):
|
||||
genre = ""
|
||||
genres = item.get("Genres")
|
||||
if genres != None and genres != []:
|
||||
for genre_string in genres:
|
||||
if genre == "": #Just take the first genre
|
||||
genre = genre_string
|
||||
else:
|
||||
genre = genre + " / " + genre_string
|
||||
elif item.get("SeriesGenres") != None and item.get("SeriesGenres") != '':
|
||||
genres = item.get("SeriesGenres")
|
||||
if genres != None and genres != []:
|
||||
for genre_string in genres:
|
||||
if genre == "": #Just take the first genre
|
||||
genre = genre_string
|
||||
else:
|
||||
genre = genre + " / " + genre_string
|
||||
return genre
|
||||
|
||||
def getName(self, item):
|
||||
Temp = item.get("Name")
|
||||
if Temp == None:
|
||||
Temp = ""
|
||||
Name=Temp.encode('utf-8')
|
||||
return Name
|
||||
|
||||
def getRecursiveItemCount(self, item):
|
||||
if item.get("RecursiveItemCount") != None:
|
||||
return str(item.get("RecursiveItemCount"))
|
||||
else:
|
||||
return "0"
|
||||
|
||||
def getSeriesName(self, item):
|
||||
Temp = item.get("SeriesName")
|
||||
if Temp == None:
|
||||
Temp = ""
|
||||
Name=Temp.encode('utf-8')
|
||||
return Name
|
||||
|
||||
def getOverview(self, item):
|
||||
Temp = item.get("Overview")
|
||||
if Temp == None:
|
||||
Temp=''
|
||||
Overview1=Temp.encode('utf-8')
|
||||
Overview=str(Overview1)
|
||||
return Overview
|
||||
|
||||
def getPremiereDate(self, item):
|
||||
if(item.get("PremiereDate") != None):
|
||||
premieredatelist = (item.get("PremiereDate")).split("T")
|
||||
premieredate = premieredatelist[0]
|
||||
else:
|
||||
premieredate = ""
|
||||
Temp = premieredate
|
||||
premieredate = Temp.encode('utf-8')
|
||||
return premieredate
|
||||
|
||||
def getTVInfo(self, item, userData):
|
||||
TotalSeasons = 0 if item.get("ChildCount")==None else item.get("ChildCount")
|
||||
TotalEpisodes = 0 if item.get("RecursiveItemCount")==None else item.get("RecursiveItemCount")
|
||||
WatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else TotalEpisodes-int(userData.get("UnplayedItemCount"))
|
||||
UnWatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else int(userData.get("UnplayedItemCount"))
|
||||
NumEpisodes = TotalEpisodes
|
||||
tempEpisode = ""
|
||||
if (item.get("IndexNumber") != None):
|
||||
episodeNum = item.get("IndexNumber")
|
||||
if episodeNum < 10:
|
||||
tempEpisode = "0" + str(episodeNum)
|
||||
else:
|
||||
tempEpisode = str(episodeNum)
|
||||
|
||||
tempSeason = ""
|
||||
if (str(item.get("ParentIndexNumber")) != None):
|
||||
tempSeason = str(item.get("ParentIndexNumber"))
|
||||
if item.get("ParentIndexNumber") < 10:
|
||||
tempSeason = "0" + tempSeason
|
||||
if item.get("SeriesName") != None:
|
||||
temp=item.get("SeriesName")
|
||||
SeriesName=temp.encode('utf-8')
|
||||
else:
|
||||
SeriesName=''
|
||||
return {'TotalSeasons' : str(TotalSeasons),
|
||||
'TotalEpisodes' : str(TotalEpisodes),
|
||||
'WatchedEpisodes' : str(WatchedEpisodes),
|
||||
'UnWatchedEpisodes': str(UnWatchedEpisodes),
|
||||
'NumEpisodes' : str(NumEpisodes),
|
||||
'Season' : tempSeason,
|
||||
'Episode' : tempEpisode,
|
||||
'SeriesName' : SeriesName
|
||||
}
|
||||
def getDate(self, item):
|
||||
tempDate = item.get("DateCreated")
|
||||
if tempDate != None:
|
||||
tempDate = tempDate.split("T")[0]
|
||||
date = tempDate.split("-")
|
||||
tempDate = date[2] + "." + date[1] + "." +date[0]
|
||||
else:
|
||||
tempDate = "01.01.2000"
|
||||
return tempDate
|
52
resources/lib/ClientInformation.py
Normal file
52
resources/lib/ClientInformation.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
from uuid import uuid4 as uuid4
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
|
||||
class ClientInformation():
|
||||
|
||||
def getMachineId(self):
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
clientId = WINDOW.getProperty("client_id")
|
||||
self.addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
if(clientId == None or clientId == ""):
|
||||
xbmc.log("CLIENT_ID - > No Client ID in WINDOW")
|
||||
clientId = self.addonSettings.getSetting('client_id')
|
||||
|
||||
if(clientId == None or clientId == ""):
|
||||
xbmc.log("CLIENT_ID - > No Client ID in SETTINGS")
|
||||
uuid = uuid4()
|
||||
clientId = str("%012X" % uuid)
|
||||
WINDOW.setProperty("client_id", clientId)
|
||||
self.addonSettings.setSetting('client_id', clientId)
|
||||
xbmc.log("CLIENT_ID - > New Client ID : " + clientId)
|
||||
else:
|
||||
WINDOW.setProperty('client_id', clientId)
|
||||
xbmc.log("CLIENT_ID - > Client ID saved to WINDOW from Settings : " + clientId)
|
||||
|
||||
return clientId
|
||||
|
||||
def getVersion(self):
|
||||
version = xbmcaddon.Addon(id="plugin.video.mb3sync").getAddonInfo("version")
|
||||
return version
|
||||
|
||||
|
||||
def getPlatform(self):
|
||||
|
||||
if xbmc.getCondVisibility('system.platform.osx'):
|
||||
return "OSX"
|
||||
elif xbmc.getCondVisibility('system.platform.atv2'):
|
||||
return "ATV2"
|
||||
elif xbmc.getCondVisibility('system.platform.ios'):
|
||||
return "iOS"
|
||||
elif xbmc.getCondVisibility('system.platform.windows'):
|
||||
return "Windows"
|
||||
elif xbmc.getCondVisibility('system.platform.linux'):
|
||||
return "Linux/RPi"
|
||||
elif xbmc.getCondVisibility('system.platform.android'):
|
||||
return "Linux/Android"
|
||||
|
||||
return "Unknown"
|
147
resources/lib/ConnectionManager.py
Normal file
147
resources/lib/ConnectionManager.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
#################################################################################################
|
||||
# connection manager class
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from DownloadUtils import DownloadUtils
|
||||
import urllib
|
||||
import sys
|
||||
import socket
|
||||
|
||||
#define our global download utils
|
||||
logLevel = 1
|
||||
###########################################################################
|
||||
class ConnectionManager():
|
||||
|
||||
addonSettings = None
|
||||
__addon__ = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
__addondir__ = xbmc.translatePath( __addon__.getAddonInfo('profile') )
|
||||
__language__ = __addon__.getLocalizedString
|
||||
|
||||
def printDebug(self, msg, level = 1):
|
||||
if(logLevel >= level):
|
||||
if(logLevel == 2):
|
||||
try:
|
||||
xbmc.log("mb3sync " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg))
|
||||
except UnicodeEncodeError:
|
||||
xbmc.log("mb3sync " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
|
||||
else:
|
||||
try:
|
||||
xbmc.log("mb3sync " + str(level) + " -> " + str(msg))
|
||||
except UnicodeEncodeError:
|
||||
xbmc.log("mb3sync " + str(level) + " -> " + str(msg.encode('utf-8')))
|
||||
|
||||
def checkServer(self):
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
WINDOW.setProperty("Server_Checked", "True")
|
||||
|
||||
self.printDebug ("mb3sync Connection Manager Called")
|
||||
self.addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
port = self.addonSettings.getSetting('port')
|
||||
host = self.addonSettings.getSetting('ipaddress')
|
||||
|
||||
if(len(host) != 0 and host != "<none>"):
|
||||
self.printDebug ("mb3sync server already set")
|
||||
return
|
||||
|
||||
serverInfo = self.getServerDetails()
|
||||
|
||||
if(serverInfo == None):
|
||||
self.printDebug ("mb3sync getServerDetails failed")
|
||||
return
|
||||
|
||||
index = serverInfo.find(":")
|
||||
|
||||
if(index <= 0):
|
||||
self.printDebug ("mb3sync getServerDetails data not correct : " + serverInfo)
|
||||
return
|
||||
|
||||
server_address = serverInfo[:index]
|
||||
server_port = serverInfo[index+1:]
|
||||
self.printDebug ("mb3sync detected server info " + server_address + " : " + server_port)
|
||||
|
||||
xbmcgui.Dialog().ok(self.__language__(30167), self.__language__(30168), self.__language__(30169) + server_address, self.__language__(30030) + server_port)
|
||||
|
||||
# get a list of users
|
||||
self.printDebug ("Getting user list")
|
||||
jsonData = None
|
||||
downloadUtils = DownloadUtils()
|
||||
try:
|
||||
jsonData = downloadUtils.downloadUrl(server_address + ":" + server_port + "/mediabrowser/Users/Public?format=json")
|
||||
except Exception, msg:
|
||||
error = "Get User unable to connect to " + server_address + ":" + server_port + " : " + str(msg)
|
||||
xbmc.log (error)
|
||||
return ""
|
||||
|
||||
if(jsonData == False):
|
||||
return
|
||||
|
||||
self.printDebug("jsonData : " + str(jsonData), level=2)
|
||||
result = json.loads(jsonData)
|
||||
|
||||
names = []
|
||||
userList = []
|
||||
for user in result:
|
||||
name = user.get("Name")
|
||||
userList.append(name)
|
||||
if(user.get("HasPassword") == True):
|
||||
name = name + " (Secure)"
|
||||
names.append(name)
|
||||
|
||||
self.printDebug ("User List : " + str(names))
|
||||
self.printDebug ("User List : " + str(userList))
|
||||
return_value = xbmcgui.Dialog().select(self.__language__(30200), names)
|
||||
|
||||
if(return_value > -1):
|
||||
selected_user = userList[return_value]
|
||||
self.printDebug("Setting Selected User : " + selected_user)
|
||||
if self.addonSettings.getSetting("port") != server_port:
|
||||
self.addonSettings.setSetting("port", server_port)
|
||||
if self.addonSettings.getSetting("ipaddress") != server_address:
|
||||
self.addonSettings.setSetting("ipaddress", server_address)
|
||||
if self.addonSettings.getSetting("username") != selected_user:
|
||||
self.addonSettings.setSetting("username", selected_user)
|
||||
|
||||
def getServerDetails(self):
|
||||
|
||||
self.printDebug("Getting Server Details from Network")
|
||||
|
||||
MESSAGE = "who is MediaBrowserServer?"
|
||||
#MULTI_GROUP = ("224.3.29.71", 7359)
|
||||
#MULTI_GROUP = ("127.0.0.1", 7359)
|
||||
MULTI_GROUP = ("<broadcast>", 7359)
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.settimeout(6.0)
|
||||
|
||||
#ttl = struct.pack('b', 20)
|
||||
#sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
|
||||
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
|
||||
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
|
||||
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
|
||||
|
||||
xbmc.log("MutliGroup : " + str(MULTI_GROUP));
|
||||
xbmc.log("Sending UDP Data : " + MESSAGE);
|
||||
sock.sendto(MESSAGE, MULTI_GROUP)
|
||||
|
||||
try:
|
||||
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
|
||||
xbmc.log("Received Response : " + data)
|
||||
if(data[0:18] == "MediaBrowserServer"):
|
||||
xbmc.log("Found Server : " + data[19:])
|
||||
return data[19:]
|
||||
except:
|
||||
xbmc.log("No UDP Response")
|
||||
pass
|
||||
|
||||
return None
|
||||
|
594
resources/lib/DownloadUtils.py
Normal file
594
resources/lib/DownloadUtils.py
Normal file
|
@ -0,0 +1,594 @@
|
|||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import urllib
|
||||
import urllib2
|
||||
import httplib
|
||||
import hashlib
|
||||
import StringIO
|
||||
import gzip
|
||||
import sys
|
||||
import inspect
|
||||
import json as json
|
||||
from random import randrange
|
||||
from uuid import uuid4 as uuid4
|
||||
from ClientInformation import ClientInformation
|
||||
import Utils as utils
|
||||
import encodings
|
||||
import time
|
||||
import traceback
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
getString = addonSettings.getLocalizedString
|
||||
|
||||
class DownloadUtils():
|
||||
|
||||
logLevel = 0
|
||||
getString = None
|
||||
LogCalls = False
|
||||
TrackLog = ""
|
||||
TotalUrlCalls = 0
|
||||
|
||||
def __init__(self, *args):
|
||||
pass
|
||||
|
||||
def getServer(self):
|
||||
port = addonSettings.getSetting('port')
|
||||
host = addonSettings.getSetting('ipaddress')
|
||||
return host + ":" + port
|
||||
|
||||
def getUserId(self, suppress=True):
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
port = addonSettings.getSetting('port')
|
||||
host = addonSettings.getSetting('ipaddress')
|
||||
userName = addonSettings.getSetting('username')
|
||||
|
||||
userid = WINDOW.getProperty("userid" + userName)
|
||||
|
||||
if(userid != None and userid != ""):
|
||||
utils.logMsg("MB3 Sync","DownloadUtils -> Returning saved UserID : " + userid + "UserName: " + userName)
|
||||
return userid
|
||||
|
||||
utils.logMsg("MB3 Sync","Looking for user name: " + userName)
|
||||
|
||||
authOk = self.authenticate()
|
||||
if(authOk == ""):
|
||||
if(suppress == False):
|
||||
xbmcgui.Dialog().ok(getString(30044), getString(30044))
|
||||
return ""
|
||||
|
||||
userid = WINDOW.getProperty("userid"+ userName)
|
||||
if(userid == "" and suppress == False):
|
||||
xbmcgui.Dialog().ok(getString(30045),getString(30045))
|
||||
|
||||
utils.logMsg("MB3 Sync","userid : " + userid)
|
||||
self.postcapabilities()
|
||||
|
||||
return userid
|
||||
|
||||
def postcapabilities(self):
|
||||
utils.logMsg("MB3 Sync","postcapabilities called")
|
||||
|
||||
# Set Capabilities
|
||||
mb3Port = addonSettings.getSetting('port')
|
||||
mb3Host = addonSettings.getSetting('ipaddress')
|
||||
clientInfo = ClientInformation()
|
||||
machineId = clientInfo.getMachineId()
|
||||
|
||||
# get session id
|
||||
url = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Sessions?DeviceId=" + machineId + "&format=json"
|
||||
utils.logMsg("MB3 Sync","Session URL : " + url);
|
||||
jsonData = self.downloadUrl(url)
|
||||
utils.logMsg("MB3 Sync","Session JsonData : " + jsonData)
|
||||
result = json.loads(jsonData)
|
||||
utils.logMsg("MB3 Sync","Session JsonData : " + str(result))
|
||||
sessionId = result[0].get("Id")
|
||||
utils.logMsg("MB3 Sync","Session Id : " + str(sessionId))
|
||||
|
||||
# post capability data
|
||||
playableMediaTypes = "Audio,Video,Photo"
|
||||
supportedCommands = "Play,Playstate,DisplayContent,GoHome,SendString,GoToSettings,DisplayMessage,PlayNext"
|
||||
|
||||
url = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Sessions/Capabilities?Id=" + sessionId + "&PlayableMediaTypes=" + playableMediaTypes + "&SupportedCommands=" + supportedCommands
|
||||
|
||||
postData = {}
|
||||
#postData["Id"] = sessionId;
|
||||
#postData["PlayableMediaTypes"] = "Video";
|
||||
#postData["SupportedCommands"] = "MoveUp";
|
||||
stringdata = json.dumps(postData)
|
||||
utils.logMsg("MB3 Sync","Capabilities URL : " + url);
|
||||
utils.logMsg("MB3 Sync","Capabilities Data : " + stringdata)
|
||||
|
||||
self.downloadUrl(url, postBody=stringdata, type="POST")
|
||||
|
||||
def authenticate(self):
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
token = WINDOW.getProperty("AccessToken"+addonSettings.getSetting('username'))
|
||||
if(token != None and token != ""):
|
||||
utils.logMsg("MB3 Sync","DownloadUtils -> Returning saved AccessToken for user : " + addonSettings.getSetting('username') + " token: "+ token)
|
||||
return token
|
||||
|
||||
port = addonSettings.getSetting("port")
|
||||
host = addonSettings.getSetting("ipaddress")
|
||||
if(host == None or host == "" or port == None or port == ""):
|
||||
return ""
|
||||
|
||||
url = "http://" + addonSettings.getSetting("ipaddress") + ":" + addonSettings.getSetting("port") + "/mediabrowser/Users/AuthenticateByName?format=json"
|
||||
|
||||
clientInfo = ClientInformation()
|
||||
txt_mac = clientInfo.getMachineId()
|
||||
version = clientInfo.getVersion()
|
||||
|
||||
deviceName = addonSettings.getSetting('deviceName')
|
||||
deviceName = deviceName.replace("\"", "_")
|
||||
|
||||
authString = "Mediabrowser Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
|
||||
headers = {'Accept-encoding': 'gzip', 'Authorization' : authString}
|
||||
|
||||
if addonSettings.getSetting('password') !=None and addonSettings.getSetting('password') !='':
|
||||
sha1 = hashlib.sha1(addonSettings.getSetting('password'))
|
||||
sha1 = sha1.hexdigest()
|
||||
else:
|
||||
sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
|
||||
|
||||
messageData = "username=" + addonSettings.getSetting('username') + "&password=" + sha1
|
||||
|
||||
resp = self.downloadUrl(url, postBody=messageData, type="POST", authenticate=False, suppress=True)
|
||||
|
||||
result = None
|
||||
accessToken = None
|
||||
try:
|
||||
result = json.loads(resp)
|
||||
accessToken = result.get("AccessToken")
|
||||
except:
|
||||
pass
|
||||
|
||||
if(result != None and accessToken != None):
|
||||
utils.logMsg("MB3 Sync","User Authenticated : " + accessToken)
|
||||
WINDOW.setProperty("AccessToken"+addonSettings.getSetting('username'), accessToken)
|
||||
WINDOW.setProperty("userid"+addonSettings.getSetting('username'), result.get("User").get("Id"))
|
||||
WINDOW.setProperty("mb3_authenticated", "true")
|
||||
return accessToken
|
||||
else:
|
||||
utils.logMsg("MB3 Sync","User NOT Authenticated")
|
||||
WINDOW.setProperty("AccessToken"+addonSettings.getSetting('username'), "")
|
||||
WINDOW.setProperty("mb3_authenticated", "false")
|
||||
return ""
|
||||
|
||||
def getArtwork(self, data, type, index = "0", userParentInfo = False):
|
||||
|
||||
id = data.get("Id")
|
||||
getSeriesData = False
|
||||
userData = data.get("UserData")
|
||||
|
||||
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 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 != "Primary2" and type != "Primary3" and type != "Primary4" and type != "Thumb" and type != "Banner" and type!="Thumb3":
|
||||
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" and type != "Primary2" and type != "Primary3" and type != "Primary4":
|
||||
id = data.get("SeriesId")
|
||||
getSeriesData = True
|
||||
if type =="Primary2" or type=="Primary3" or type=="Primary4":
|
||||
id = data.get("SeasonId")
|
||||
getSeriesData = True
|
||||
if data.get("SeasonUserData") != None:
|
||||
userData = data.get("SeasonUserData")
|
||||
if id == None:
|
||||
id=data.get("Id")
|
||||
|
||||
imageTag = "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format
|
||||
originalType = type
|
||||
if type == "Primary2" or type == "Primary3" or type == "Primary4" or type=="SeriesPrimary":
|
||||
type = "Primary"
|
||||
if type == "Backdrop2" or type=="Backdrop3" or type=="BackdropNoIndicators":
|
||||
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)
|
||||
|
||||
if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Logo":
|
||||
imageTag = data.get("ParentLogoImageTag")
|
||||
if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Art":
|
||||
imageTag = data.get("ParentArtImageTag")
|
||||
if (data.get("Type") == "Episode") and originalType=="Thumb3":
|
||||
imageTag = data.get("SeriesThumbImageTag")
|
||||
if (data.get("Type") == "Season") and originalType=="Thumb3" and imageTag=="e3ab56fe27d389446754d0fb04910a34" :
|
||||
imageTag = data.get("ParentThumbImageTag")
|
||||
id = data.get("SeriesId")
|
||||
|
||||
query = ""
|
||||
height = "10000"
|
||||
width = "10000"
|
||||
played = "0"
|
||||
totalbackdrops = 0
|
||||
|
||||
if addonSettings.getSetting('showArtIndicators')=='true': # add watched, unplayedcount and percentage played indicators to posters
|
||||
if (originalType =="Primary" or originalType =="Backdrop" or originalType =="Banner") and data.get("Type") != "Episode":
|
||||
if originalType =="Backdrop" and index == "0" and data.get("BackdropImageTags") != None:
|
||||
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 addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
|
||||
if(userData != None and userData.get("Played") == True and 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 addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
elif originalType =="Primary2":
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and 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 addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "338"
|
||||
width = "226"
|
||||
|
||||
elif originalType =="Primary3" or originalType == "SeriesPrimary":
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and 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 addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
|
||||
|
||||
elif originalType =="Primary4":
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and 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 addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "270"
|
||||
width = "180"
|
||||
|
||||
elif type =="Primary" and data.get("Type") == "Episode":
|
||||
if userData != None:
|
||||
|
||||
UnWatched = 0 if userData.get("UnplayedItemCount")==None else userData.get("UnplayedItemCount")
|
||||
|
||||
if UnWatched <> 0 and addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and 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 addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "410"
|
||||
width = "770"
|
||||
|
||||
|
||||
elif originalType =="Backdrop2" or originalType =="Thumb2" and data.get("Type") != "Episode":
|
||||
if originalType =="Backdrop2" and data.get("BackdropImageTags") != None:
|
||||
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 addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and 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 addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "370"
|
||||
width = "660"
|
||||
|
||||
elif originalType =="Backdrop3" or originalType =="Thumb3" and data.get("Type") != "Episode":
|
||||
if originalType =="Backdrop3" and data.get("BackdropImageTags") != None:
|
||||
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 addonSettings.getSetting('showUnplayedIndicators')=='true':
|
||||
query = query + "&UnplayedCount=" + str(UnWatched)
|
||||
|
||||
if(userData != None and userData.get("Played") == True and 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 addonSettings.getSetting('showPlayedPrecentageIndicators')=='true':
|
||||
played = str(PlayedPercentage)
|
||||
|
||||
height = "910"
|
||||
width = "1620"
|
||||
|
||||
if originalType =="BackdropNoIndicators" and index == "0" and data.get("BackdropImageTags") != None:
|
||||
totalbackdrops = len(data.get("BackdropImageTags"))
|
||||
if totalbackdrops != 0:
|
||||
index = str(randrange(0,totalbackdrops))
|
||||
# use the local image proxy server that is made available by this addons service
|
||||
|
||||
port = addonSettings.getSetting('port')
|
||||
host = addonSettings.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
if addonSettings.getSetting('compressArt')=='true':
|
||||
query = query + "&Quality=90"
|
||||
|
||||
if imageTag == None:
|
||||
imageTag = "e3ab56fe27d389446754d0fb04910a34"
|
||||
artwork = "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + index + "/" + imageTag + "/original/" + width + "/" + height + "/" + played + "?" + query
|
||||
if addonSettings.getSetting('disableCoverArt')=='true':
|
||||
artwork = artwork + "&EnableImageEnhancers=false"
|
||||
|
||||
utils.logMsg("MB3 Sync","getArtwork : " + artwork, level=2)
|
||||
|
||||
# do not return non-existing images
|
||||
if ( (type!="Backdrop" and imageTag=="e3ab56fe27d389446754d0fb04910a34") | #Remember, this is the placeholder tag, meaning we didn't find a valid tag
|
||||
(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" or (type=="Backdrop" and getSeriesData==True and data.get("ParentBackdropImageTags") == None) or (type=="Backdrop" and getSeriesData!=True):
|
||||
artwork=''
|
||||
|
||||
return artwork
|
||||
|
||||
def getUserArtwork(self, data, type, index = "0"):
|
||||
|
||||
id = data.get("Id")
|
||||
|
||||
port = addonSettings.getSetting('port')
|
||||
host = addonSettings.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
artwork = "http://" + server + "/mediabrowser/Users/" + str(id) + "/Images/" + type + "?Format=original"
|
||||
|
||||
return artwork
|
||||
|
||||
def imageUrl(self, id, type, index, width, height):
|
||||
|
||||
port = addonSettings.getSetting('port')
|
||||
host = addonSettings.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
|
||||
return "http://" + server + "/mediabrowser/Items/" + str(id) + "/Images/" + type + "/" + str(index) + "/e3ab56fe27d389446754d0fb04910a34/original/" + str(width) + "/" + str(height) + "/0"
|
||||
|
||||
def getAuthHeader(self, authenticate=True):
|
||||
clientInfo = ClientInformation()
|
||||
txt_mac = clientInfo.getMachineId()
|
||||
version = clientInfo.getVersion()
|
||||
|
||||
deviceName = addonSettings.getSetting('deviceName')
|
||||
deviceName = deviceName.replace("\"", "_")
|
||||
|
||||
if(authenticate == False):
|
||||
authString = "MediaBrowser Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
|
||||
headers = {"Accept-encoding": "gzip", "Accept-Charset" : "UTF-8,*", "Authorization" : authString}
|
||||
return headers
|
||||
else:
|
||||
userid = self.getUserId()
|
||||
authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\""
|
||||
headers = {"Accept-encoding": "gzip", "Accept-Charset" : "UTF-8,*", "Authorization" : authString}
|
||||
|
||||
authToken = self.authenticate()
|
||||
if(authToken != ""):
|
||||
headers["X-MediaBrowser-Token"] = authToken
|
||||
|
||||
utils.logMsg("MB3 Sync","Authentication Header : " + str(headers))
|
||||
return headers
|
||||
|
||||
def downloadUrl(self, url, suppress=False, postBody=None, type="GET", popup=0, authenticate=True ):
|
||||
utils.logMsg("MB3 Sync","== ENTER: getURL ==")
|
||||
|
||||
self.TotalUrlCalls = self.TotalUrlCalls + 1
|
||||
if(self.LogCalls):
|
||||
stackString = ""
|
||||
for f in inspect.stack():
|
||||
stackString = stackString + "\r - " + str(f)
|
||||
self.TrackLog = self.TrackLog + "HTTP_API_CALL : " + url + stackString + "\r"
|
||||
|
||||
link = ""
|
||||
try:
|
||||
if url[0:4] == "http":
|
||||
serversplit = 2
|
||||
urlsplit = 3
|
||||
else:
|
||||
serversplit = 0
|
||||
urlsplit = 1
|
||||
|
||||
server = url.split('/')[serversplit]
|
||||
urlPath = "/"+"/".join(url.split('/')[urlsplit:])
|
||||
|
||||
utils.logMsg("MB3 Sync","DOWNLOAD_URL = " + url)
|
||||
utils.logMsg("MB3 Sync","server = "+str(server), level=2)
|
||||
utils.logMsg("MB3 Sync","urlPath = "+str(urlPath), level=2)
|
||||
|
||||
conn = httplib.HTTPConnection(server, timeout=5)
|
||||
|
||||
head = self.getAuthHeader(authenticate)
|
||||
utils.logMsg("MB3 Sync","HEADERS : " + str(head), level=1)
|
||||
|
||||
# make the connection and send the request
|
||||
if(postBody != None):
|
||||
head["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
head["Content-Length"] = str(len(postBody))
|
||||
utils.logMsg("MB3 Sync","POST DATA : " + postBody)
|
||||
conn.request(method=type, url=urlPath, body=postBody, headers=head)
|
||||
else:
|
||||
conn.request(method=type, url=urlPath, headers=head)
|
||||
|
||||
# get the response
|
||||
tries = 0
|
||||
while tries <= 4:
|
||||
try:
|
||||
data = conn.getresponse()
|
||||
break
|
||||
except:
|
||||
# TODO: we need to work out which errors we can just quit trying immediately
|
||||
if(xbmc.abortRequested == True):
|
||||
return ""
|
||||
xbmc.sleep(100)
|
||||
if(xbmc.abortRequested == True):
|
||||
return ""
|
||||
tries += 1
|
||||
if tries == 5:
|
||||
data = conn.getresponse()
|
||||
|
||||
utils.logMsg("MB3 Sync","GET URL HEADERS : " + str(data.getheaders()), level=2)
|
||||
|
||||
# process the response
|
||||
contentType = "none"
|
||||
if int(data.status) == 200:
|
||||
retData = data.read()
|
||||
contentType = data.getheader('content-encoding')
|
||||
utils.logMsg("MB3 Sync","Data Len Before : " + str(len(retData)), level=2)
|
||||
if(contentType == "gzip"):
|
||||
retData = StringIO.StringIO(retData)
|
||||
gzipper = gzip.GzipFile(fileobj=retData)
|
||||
link = gzipper.read()
|
||||
else:
|
||||
link = retData
|
||||
utils.logMsg("MB3 Sync","Data Len After : " + str(len(link)), level=2)
|
||||
utils.logMsg("MB3 Sync","====== 200 returned =======", level=2)
|
||||
utils.logMsg("MB3 Sync","Content-Type : " + str(contentType), level=2)
|
||||
utils.logMsg("MB3 Sync",link, level=2)
|
||||
utils.logMsg("MB3 Sync","====== 200 finished ======", level=2)
|
||||
|
||||
elif ( int(data.status) == 301 ) or ( int(data.status) == 302 ):
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
return data.getheader('Location')
|
||||
|
||||
elif int(data.status) == 401:
|
||||
error = "HTTP response error: " + str(data.status) + " " + str(data.reason)
|
||||
xbmc.log(error)
|
||||
|
||||
WINDOW = xbmcgui.Window(10000)
|
||||
timeStamp = WINDOW.getProperty("mb3sync_LAST_USER_ERROR")
|
||||
if(timeStamp == None or timeStamp == ""):
|
||||
timeStamp = "0"
|
||||
|
||||
if((int(timeStamp) + 10) < int(time.time())):
|
||||
xbmcgui.Dialog().ok(getString(30135), getString(30044))
|
||||
WINDOW.setProperty("mb3sync_LAST_USER_ERROR", str(int(time.time())))
|
||||
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
return ""
|
||||
|
||||
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(getString(30135),server)
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
return ""
|
||||
else:
|
||||
link = ""
|
||||
except Exception, msg:
|
||||
error = "Unable to connect to " + str(server) + " : " + str(msg)
|
||||
xbmc.log(error)
|
||||
stack = self.FormatException()
|
||||
utils.logMsg("MB3 Sync",stack)
|
||||
if suppress is False:
|
||||
if popup == 0:
|
||||
xbmc.executebuiltin("XBMC.Notification(: Connection Error: Error connecting to server,)")
|
||||
else:
|
||||
xbmcgui.Dialog().ok(getString(30204), str(msg))
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
return link
|
||||
|
||||
def FormatException(self):
|
||||
exception_list = traceback.format_stack()
|
||||
exception_list = exception_list[:-2]
|
||||
exception_list.extend(traceback.format_tb(sys.exc_info()[2]))
|
||||
exception_list.extend(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1]))
|
||||
|
||||
exception_str = "Traceback (most recent call last):\n"
|
||||
exception_str += "".join(exception_list)
|
||||
# Removing the last \n
|
||||
exception_str = exception_str[:-1]
|
||||
|
||||
return exception_str
|
||||
|
||||
def __del__(self):
|
||||
return
|
||||
# xbmc.log("\rURL_REQUEST_REPORT : Total Calls : " + str(self.TotalUrlCalls) + "\r" + self.TrackLog)
|
40
resources/lib/KodiMonitor.py
Normal file
40
resources/lib/KodiMonitor.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
#################################################################################################
|
||||
# Kodi Monitor
|
||||
# Watched events that occur in Kodi, like setting media watched
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import json
|
||||
|
||||
import Utils as utils
|
||||
from LibrarySync import LibrarySync
|
||||
|
||||
librarySync = LibrarySync()
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
class Kodi_Monitor(xbmc.Monitor):
|
||||
def __init__(self, *args, **kwargs):
|
||||
xbmc.Monitor.__init__(self)
|
||||
|
||||
def onDatabaseUpdated(self, database):
|
||||
pass
|
||||
|
||||
#this library monitor is used to detect a watchedstate change by the user through the library
|
||||
def onNotification (self,sender,method,data):
|
||||
if method == "VideoLibrary.OnUpdate":
|
||||
|
||||
#check windowprop if the sync is busy to prevent any false updates
|
||||
if WINDOW.getProperty("librarysync") != "busy":
|
||||
|
||||
jsondata = json.loads(data)
|
||||
if jsondata != None:
|
||||
playcount = None
|
||||
playcount = jsondata.get("playcount")
|
||||
item = jsondata.get("item").get("id")
|
||||
|
||||
if playcount != None:
|
||||
librarySync.updatePlayCountFromKodi(item, playcount)
|
||||
|
270
resources/lib/LibrarySync.py
Normal file
270
resources/lib/LibrarySync.py
Normal file
|
@ -0,0 +1,270 @@
|
|||
#################################################################################################
|
||||
# LibrarySync
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
import json
|
||||
import threading
|
||||
import urllib
|
||||
from datetime import datetime, timedelta, time
|
||||
import urllib2
|
||||
import os
|
||||
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
|
||||
from xml.etree import ElementTree
|
||||
from xml.dom import minidom
|
||||
import xml.etree.cElementTree as ET
|
||||
|
||||
from API import API
|
||||
import Utils as utils
|
||||
from DownloadUtils import DownloadUtils
|
||||
downloadUtils = DownloadUtils()
|
||||
|
||||
addon = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
addondir = xbmc.translatePath( addon.getAddonInfo('profile') )
|
||||
dataPath = os.path.join(addondir,"library")
|
||||
movieLibrary = os.path.join(dataPath,'movies')
|
||||
tvLibrary = os.path.join(dataPath,'tvshows')
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
port = addon.getSetting('port')
|
||||
host = addon.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
userid = downloadUtils.getUserId()
|
||||
|
||||
|
||||
class LibrarySync():
|
||||
|
||||
def syncDatabase(self):
|
||||
|
||||
WINDOW.setProperty("librarysync", "busy")
|
||||
updateNeeded = False
|
||||
|
||||
allMovies = list()
|
||||
for item in self.getMovies(True):
|
||||
if not item.get('IsFolder'):
|
||||
kodiItem = self.getKodiMovie(item["Id"])
|
||||
allMovies.append(item["Id"])
|
||||
if kodiItem == None:
|
||||
self.addMovieToKodiLibrary(item)
|
||||
updateNeeded = True
|
||||
else:
|
||||
self.updateMovieToKodiLibrary(item, kodiItem)
|
||||
|
||||
cleanNeeded = False
|
||||
# process deletes
|
||||
allLocaldirs, filesMovies = xbmcvfs.listdir(movieLibrary)
|
||||
allMB3Movies = set(allMovies)
|
||||
for dir in allLocaldirs:
|
||||
if not dir in allMB3Movies:
|
||||
self.deleteMovieFromKodiLibrary(dir)
|
||||
cleanneeded = True
|
||||
|
||||
if cleanNeeded:
|
||||
xbmc.executebuiltin("CleanLibrary(video)")
|
||||
|
||||
if updateNeeded:
|
||||
xbmc.executebuiltin("UpdateLibrary(video)")
|
||||
|
||||
WINDOW.clearProperty("librarysync")
|
||||
|
||||
def updatePlayCounts(self):
|
||||
#update all playcounts from MB3 to Kodi library
|
||||
|
||||
WINDOW.setProperty("librarysync", "busy")
|
||||
|
||||
for item in self.getMovies(False):
|
||||
if not item.get('IsFolder'):
|
||||
kodiItem = self.getKodiMovie(item["Id"])
|
||||
userData=API().getUserData(item)
|
||||
timeInfo = API().getTimeInfo(item)
|
||||
if kodiItem != None:
|
||||
if kodiItem['playcount'] != int(userData.get("PlayCount")):
|
||||
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "playcount": %i}, "id": 1 }' %(kodiItem['movieid'], int(userData.get("PlayCount"))))
|
||||
if kodiItem['playcount'] != int(userData.get("PlayCount")):
|
||||
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "playcount": %i}, "id": 1 }' %(kodiItem['movieid'], int(userData.get("PlayCount"))))
|
||||
|
||||
|
||||
WINDOW.clearProperty("librarysync")
|
||||
|
||||
def getMovies(self, fullinfo = False):
|
||||
result = None
|
||||
if fullinfo:
|
||||
url = server + '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=Path,Genres,Studios,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Movie&format=json&ImageTypeLimit=1'
|
||||
else:
|
||||
url = server + '/mediabrowser/Users/' + userid + '/Items?&SortBy=SortName&Fields=CumulativeRunTimeTicks&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Movie&format=json&ImageTypeLimit=1'
|
||||
|
||||
jsonData = downloadUtils.downloadUrl(url, suppress=True, popup=0)
|
||||
if jsonData != None:
|
||||
result = json.loads(jsonData)
|
||||
if(result.has_key('Items')):
|
||||
result = result['Items']
|
||||
|
||||
return result
|
||||
|
||||
def updatePlayCountFromKodi(self, id, playcount=0):
|
||||
#when user marks item watched from kodi interface update this to MB3
|
||||
json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovieDetails", "params": { "movieid": ' + str(id) + ', "properties" : ["playcount", "file"] }, "id": "1"}')
|
||||
if json_response != None:
|
||||
jsonobject = json.loads(json_response.decode('utf-8','replace'))
|
||||
movie = None
|
||||
if(jsonobject.has_key('result')):
|
||||
result = jsonobject['result']
|
||||
if(result.has_key('moviedetails')):
|
||||
moviedetails = result['moviedetails']
|
||||
filename = moviedetails.get("file").rpartition('\\')[2]
|
||||
mb3Id = filename.replace(".strm","")
|
||||
|
||||
watchedurl = 'http://' + host + ':' + port + '/mediabrowser/Users/' + userid + '/PlayedItems/' + mb3Id
|
||||
print "watchedurl -->" + watchedurl
|
||||
if playcount != 0:
|
||||
downloadUtils.downloadUrl(watchedurl, postBody="", type="POST")
|
||||
else:
|
||||
downloadUtils.downloadUrl(watchedurl, type="DELETE")
|
||||
|
||||
def updateMovieToKodiLibrary( self, MBitem, KodiItem ):
|
||||
|
||||
#TODO: only update the info if something is actually changed
|
||||
timeInfo = API().getTimeInfo(MBitem)
|
||||
userData=API().getUserData(MBitem)
|
||||
people = API().getPeople(MBitem)
|
||||
mediaStreams=API().getMediaStreams(MBitem)
|
||||
|
||||
thumbPath = downloadUtils.getArtwork(MBitem, "Primary")
|
||||
|
||||
utils.logMsg("Updating item to Kodi Library", MBitem["Id"] + " - " + MBitem["Name"])
|
||||
|
||||
#update artwork
|
||||
self.updateArtWork(KodiItem,"poster", downloadUtils.getArtwork(MBitem, "poster"))
|
||||
self.updateArtWork(KodiItem,"clearlogo", downloadUtils.getArtwork(MBitem, "Logo"))
|
||||
self.updateArtWork(KodiItem,"banner", downloadUtils.getArtwork(MBitem, "Banner"))
|
||||
self.updateArtWork(KodiItem,"landscape", downloadUtils.getArtwork(MBitem, "Thumb"))
|
||||
self.updateArtWork(KodiItem,"discart", downloadUtils.getArtwork(MBitem, "Disc"))
|
||||
self.updateArtWork(KodiItem,"fanart", downloadUtils.getArtwork(MBitem, "Backdrop"))
|
||||
|
||||
#update duration
|
||||
duration = (int(timeInfo.get('Duration'))*60)
|
||||
if KodiItem['runtime'] != duration:
|
||||
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "runtime": %s}, "id": 1 }' %(KodiItem['movieid'], duration))
|
||||
|
||||
#update year
|
||||
if KodiItem['year'] != MBitem.get("ProductionYear") and MBitem.get("ProductionYear") != None:
|
||||
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "year": %s}, "id": 1 }' %(KodiItem['movieid'], MBitem.get("ProductionYear")))
|
||||
|
||||
#update strm file - TODO: only update strm when path has changed
|
||||
self.createSTRM(MBitem["Id"])
|
||||
|
||||
#update nfo file - needed for testing
|
||||
nfoFile = os.path.join(movieLibrary,MBitem["Id"],MBitem["Id"] + ".nfo")
|
||||
if not xbmcvfs.exists(nfoFile):
|
||||
self.createNFO(MBitem)
|
||||
|
||||
#update playcounts
|
||||
if KodiItem['playcount'] != int(userData.get("PlayCount")):
|
||||
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "playcount": %i}, "id": 1 }' %(KodiItem['movieid'], int(userData.get("PlayCount"))))
|
||||
|
||||
|
||||
def updateArtWork(self,KodiItem,artWorkName,artworkValue):
|
||||
if KodiItem['art'].has_key(artWorkName):
|
||||
if KodiItem['art'][artWorkName] != artworkValue and artworkValue != None:
|
||||
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.SetMovieDetails", "params": { "movieid": %i, "art": { "%s": "%s" }}, "id": 1 }' %(KodiItem['movieid'], artWorkName, artworkValue))
|
||||
|
||||
|
||||
def createSTRM(self,id):
|
||||
|
||||
itemPath = os.path.join(movieLibrary,id)
|
||||
if not xbmcvfs.exists(itemPath):
|
||||
xbmcvfs.mkdir(itemPath)
|
||||
|
||||
strmFile = os.path.join(itemPath,id + ".strm")
|
||||
text_file = open(strmFile, "w")
|
||||
|
||||
playUrl = "plugin://plugin.video.mb3sync/?id=" + id + '&mode=play'
|
||||
|
||||
text_file.writelines(playUrl)
|
||||
text_file.close()
|
||||
|
||||
def createNFO(self,item):
|
||||
timeInfo = API().getTimeInfo(item)
|
||||
userData=API().getUserData(item)
|
||||
people = API().getPeople(item)
|
||||
mediaStreams=API().getMediaStreams(item)
|
||||
|
||||
#todo: change path if type is not movie
|
||||
itemPath = os.path.join(movieLibrary,item["Id"])
|
||||
nfoFile = os.path.join(itemPath,item["Id"] + ".nfo")
|
||||
|
||||
root = Element("movie")
|
||||
SubElement(root, "id").text = item["Id"]
|
||||
SubElement(root, "tag").text = item["Id"]
|
||||
SubElement(root, "thumb").text = downloadUtils.getArtwork(item, "poster")
|
||||
SubElement(root, "fanart").text = timeInfo.get('Backdrop')
|
||||
SubElement(root, "title").text = item["Name"].encode('utf-8').decode('utf-8')
|
||||
SubElement(root, "originaltitle").text = item["Id"]
|
||||
|
||||
SubElement(root, "year").text = str(item.get("ProductionYear"))
|
||||
SubElement(root, "runtime").text = str(timeInfo.get('Duration'))
|
||||
|
||||
fileinfo = SubElement(root, "fileinfo")
|
||||
streamdetails = SubElement(fileinfo, "streamdetails")
|
||||
video = SubElement(streamdetails, "video")
|
||||
SubElement(video, "duration").text = str(timeInfo.get('totaltime'))
|
||||
SubElement(video, "aspect").text = timeInfo.get('aspectratio')
|
||||
SubElement(video, "codec").text = timeInfo.get('videocodec')
|
||||
SubElement(video, "width").text = str(timeInfo.get('width'))
|
||||
SubElement(video, "height").text = str(timeInfo.get('height'))
|
||||
audio = SubElement(streamdetails, "audio")
|
||||
SubElement(audio, "codec").text = timeInfo.get('audiocodec')
|
||||
SubElement(audio, "channels").text = timeInfo.get('channels')
|
||||
|
||||
SubElement(root, "plot").text = API().getOverview(item).decode('utf-8')
|
||||
|
||||
art = SubElement(root, "art")
|
||||
SubElement(art, "poster").text = downloadUtils.getArtwork(item, "poster")
|
||||
SubElement(art, "fanart").text = downloadUtils.getArtwork(item, "Backdrop")
|
||||
SubElement(art, "landscape").text = downloadUtils.getArtwork(item, "Thumb")
|
||||
SubElement(art, "clearlogo").text = downloadUtils.getArtwork(item, "Logo")
|
||||
SubElement(art, "discart").text = downloadUtils.getArtwork(item, "Disc")
|
||||
SubElement(art, "banner").text = downloadUtils.getArtwork(item, "Banner")
|
||||
|
||||
ET.ElementTree(root).write(nfoFile, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
def addMovieToKodiLibrary( self, item ):
|
||||
itemPath = os.path.join(movieLibrary,item["Id"])
|
||||
strmFile = os.path.join(itemPath,item["Id"] + ".strm")
|
||||
|
||||
utils.logMsg("Adding item to Kodi Library",item["Id"] + " - " + item["Name"])
|
||||
|
||||
#create path if not exists
|
||||
if not xbmcvfs.exists(itemPath):
|
||||
xbmcvfs.mkdir(itemPath)
|
||||
|
||||
#create nfo file
|
||||
self.createNFO(item)
|
||||
|
||||
# create strm file
|
||||
self.createSTRM(item["Id"])
|
||||
|
||||
def deleteMovieFromKodiLibrary(self, id ):
|
||||
kodiItem = self.getKodiMovie(id)
|
||||
utils.logMsg("deleting movie from Kodi library",id)
|
||||
if kodiItem != None:
|
||||
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.RemoveMovie", "params": { "movieid": %i}, "id": 1 }' %(kodiItem["movieid"]))
|
||||
|
||||
path = os.path.join(movieLibrary,id)
|
||||
xbmcvfs.rmdir(path)
|
||||
|
||||
def getKodiMovie(self, id):
|
||||
json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": { "filter": {"operator": "contains", "field": "path", "value": "' + id + '"}, "properties" : ["art", "rating", "thumbnail", "runtime", "year", "plot", "playcount", "file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMovies"}')
|
||||
jsonobject = json.loads(json_response.decode('utf-8','replace'))
|
||||
movie = None
|
||||
|
||||
if(jsonobject.has_key('result')):
|
||||
result = jsonobject['result']
|
||||
if(result.has_key('movies')):
|
||||
movies = result['movies']
|
||||
movie = movies[0]
|
||||
|
||||
return movie
|
170
resources/lib/PlayUtils.py
Normal file
170
resources/lib/PlayUtils.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
#################################################################################################
|
||||
# utils class
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from DownloadUtils import DownloadUtils
|
||||
from ClientInformation import ClientInformation
|
||||
import urllib
|
||||
import sys
|
||||
import os
|
||||
|
||||
#define our global download utils
|
||||
downloadUtils = DownloadUtils()
|
||||
clientInfo = ClientInformation()
|
||||
|
||||
###########################################################################
|
||||
class PlayUtils():
|
||||
|
||||
def getPlayUrl(self, server, id, result):
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
# if the path is local and depending on the video quality play we can direct play it do so-
|
||||
if self.isDirectPlay(result) == True:
|
||||
xbmc.log("mb3sync 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'))
|
||||
|
||||
else:
|
||||
#No path or has a path but not sufficient network so transcode
|
||||
xbmc.log("mb3sync getPlayUrl -> Transcode")
|
||||
if result.get("Type") == "Audio":
|
||||
playurl = 'http://' + server + '/mediabrowser/Audio/' + id + '/stream.mp3'
|
||||
else:
|
||||
txt_mac = clientInfo.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('DefaultSubtitleStreamIndex'))
|
||||
return playurl.encode('utf-8')
|
||||
|
||||
# Works out if we are direct playing or not
|
||||
def isDirectPlay(self, result):
|
||||
if (self.fileExists(result) or (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("mb3sync isNetworkQualitySufficient -> FALSE bit rate - settingsVideoBitRate: " + str(settingsVideoBitRate) + " mediasource bitrate: " + str(mediaSources[0].get('Bitrate')))
|
||||
return False
|
||||
else:
|
||||
xbmc.log("mb3sync isNetworkQualitySufficient -> TRUE bit rate")
|
||||
return True
|
||||
|
||||
# Any thing else is ok
|
||||
xbmc.log("mb3sync isNetworkQualitySufficient -> TRUE default")
|
||||
return True
|
||||
|
||||
|
||||
# get the addon video quality
|
||||
def getVideoBitRate(self):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
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'
|
||||
|
||||
def fileExists(self, result):
|
||||
path=result.get("Path").encode('utf-8')
|
||||
if os.path.exists(path) == True:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# Works out if the network quality can play directly or if transcoding is needed
|
||||
def isLocalPath(self, result):
|
||||
path=result.get("Path").encode('utf-8')
|
||||
playurl = 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
|
||||
|
180
resources/lib/PlaybackUtils.py
Normal file
180
resources/lib/PlaybackUtils.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
|
||||
import xbmc
|
||||
import xbmcplugin
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import urllib
|
||||
import datetime
|
||||
import time
|
||||
import json as json
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
from DownloadUtils import DownloadUtils
|
||||
downloadUtils = DownloadUtils()
|
||||
from PlayUtils import PlayUtils
|
||||
from API import API
|
||||
import Utils as utils
|
||||
|
||||
addon = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
language = addon.getLocalizedString
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
port = addon.getSetting('port')
|
||||
host = addon.getSetting('ipaddress')
|
||||
server = host + ":" + port
|
||||
userid = downloadUtils.getUserId()
|
||||
|
||||
|
||||
class PlaybackUtils():
|
||||
|
||||
settings = None
|
||||
language = None
|
||||
logLevel = 0
|
||||
|
||||
|
||||
def __init__(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
def PLAY(self, id):
|
||||
|
||||
jsonData = downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items/" + id + "?format=json&ImageTypeLimit=1", suppress=False, popup=1 )
|
||||
result = json.loads(jsonData)
|
||||
|
||||
userData = result.get("UserData")
|
||||
resume_result = 0
|
||||
seekTime = 0
|
||||
|
||||
if userData.get("PlaybackPositionTicks") != 0:
|
||||
reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
|
||||
seekTime = reasonableTicks / 10000
|
||||
displayTime = str(datetime.timedelta(seconds=seekTime))
|
||||
display_list = [ language(30106) + ' ' + displayTime, language(30107)]
|
||||
resumeScreen = xbmcgui.Dialog()
|
||||
resume_result = resumeScreen.select(language(30105), display_list)
|
||||
|
||||
|
||||
playurl = PlayUtils().getPlayUrl(server, id, result)
|
||||
xbmc.log("Play URL: " + playurl)
|
||||
thumbPath = downloadUtils.getArtwork(result, "Primary")
|
||||
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumbPath, thumbnailImage=thumbPath)
|
||||
|
||||
self.setListItemProps(server, id, listItem, result)
|
||||
|
||||
# Can not play virtual items
|
||||
if (result.get("LocationType") == "Virtual"):
|
||||
xbmcgui.Dialog().ok(self.language(30128), language(30129))
|
||||
|
||||
watchedurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayedItems/' + id
|
||||
positionurl = 'http://' + server + '/mediabrowser/Users/'+ userid + '/PlayingItems/' + id
|
||||
deleteurl = 'http://' + server + '/mediabrowser/Items/' + id
|
||||
|
||||
# set the current playing info
|
||||
WINDOW.setProperty(playurl+"watchedurl", watchedurl)
|
||||
WINDOW.setProperty(playurl+"positionurl", positionurl)
|
||||
WINDOW.setProperty(playurl+"deleteurl", "")
|
||||
WINDOW.setProperty(playurl+"deleteurl", deleteurl)
|
||||
if resume_result == 0:
|
||||
WINDOW.setProperty(playurl+"seektime", str(seekTime))
|
||||
else:
|
||||
WINDOW.clearProperty(playurl+"seektime")
|
||||
|
||||
if result.get("Type")=="Episode":
|
||||
WINDOW.setProperty(playurl+"refresh_id", result.get("SeriesId"))
|
||||
else:
|
||||
WINDOW.setProperty(playurl+"refresh_id", id)
|
||||
|
||||
WINDOW.setProperty(playurl+"runtimeticks", str(result.get("RunTimeTicks")))
|
||||
WINDOW.setProperty(playurl+"type", result.get("Type"))
|
||||
WINDOW.setProperty(playurl+"item_id", id)
|
||||
|
||||
if PlayUtils().isDirectPlay(result) == True:
|
||||
playMethod = "DirectPlay"
|
||||
else:
|
||||
playMethod = "Transcode"
|
||||
|
||||
|
||||
WINDOW.setProperty(playurl+"playmethod", playMethod)
|
||||
|
||||
mediaSources = result.get("MediaSources")
|
||||
if(mediaSources != None):
|
||||
if mediaSources[0].get('DefaultAudioStreamIndex') != None:
|
||||
WINDOW.setProperty(playurl+"AudioStreamIndex", str(mediaSources[0].get('DefaultAudioStreamIndex')))
|
||||
if mediaSources[0].get('DefaultSubtitleStreamIndex') != None:
|
||||
WINDOW.setProperty(playurl+"SubtitleStreamIndex", str(mediaSources[0].get('DefaultSubtitleStreamIndex')))
|
||||
|
||||
#this launches the playback
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
|
||||
|
||||
|
||||
def setArt(self, list,name,path):
|
||||
if name=='thumb' or name=='fanart_image' or name=='small_poster' or name=='tiny_poster' or name == "medium_landscape" or name=='medium_poster' or name=='small_fanartimage' or name=='medium_fanartimage' or name=='fanart_noindicators':
|
||||
list.setProperty(name, path)
|
||||
else:
|
||||
list.setArt({name:path})
|
||||
return list
|
||||
|
||||
def setListItemProps(self, server, id, listItem, result):
|
||||
# set up item and item info
|
||||
userid = downloadUtils.getUserId()
|
||||
thumbID = id
|
||||
eppNum = -1
|
||||
seasonNum = -1
|
||||
tvshowTitle = ""
|
||||
|
||||
if(result.get("Type") == "Episode"):
|
||||
thumbID = result.get("SeriesId")
|
||||
seasonNum = result.get("ParentIndexNumber")
|
||||
eppNum = result.get("IndexNumber")
|
||||
tvshowTitle = result.get("SeriesName")
|
||||
|
||||
self.setArt(listItem,'poster', downloadUtils.getArtwork(result, "Primary"))
|
||||
self.setArt(listItem,'tvshow.poster', downloadUtils.getArtwork(result, "SeriesPrimary"))
|
||||
self.setArt(listItem,'clearart', downloadUtils.getArtwork(result, "Art"))
|
||||
self.setArt(listItem,'tvshow.clearart', downloadUtils.getArtwork(result, "Art"))
|
||||
self.setArt(listItem,'clearlogo', downloadUtils.getArtwork(result, "Logo"))
|
||||
self.setArt(listItem,'tvshow.clearlogo', downloadUtils.getArtwork(result, "Logo"))
|
||||
self.setArt(listItem,'discart', downloadUtils.getArtwork(result, "Disc"))
|
||||
self.setArt(listItem,'fanart_image', downloadUtils.getArtwork(result, "Backdrop"))
|
||||
self.setArt(listItem,'landscape', downloadUtils.getArtwork(result, "Thumb"))
|
||||
|
||||
listItem.setProperty('IsPlayable', 'true')
|
||||
listItem.setProperty('IsFolder', 'false')
|
||||
|
||||
# Process Studios
|
||||
studio = API().getStudio(result)
|
||||
listItem.setInfo('video', {'studio' : studio})
|
||||
|
||||
# play info
|
||||
playinformation = ''
|
||||
if PlayUtils().isDirectPlay(result) == True:
|
||||
playinformation = language(30165)
|
||||
else:
|
||||
playinformation = language(30166)
|
||||
|
||||
details = {
|
||||
'title' : result.get("Name", "Missing Name") + ' - ' + playinformation,
|
||||
'plot' : result.get("Overview")
|
||||
}
|
||||
|
||||
if(eppNum > -1):
|
||||
details["episode"] = str(eppNum)
|
||||
|
||||
if(seasonNum > -1):
|
||||
details["season"] = str(seasonNum)
|
||||
|
||||
if tvshowTitle != None:
|
||||
details["TVShowTitle"] = tvshowTitle
|
||||
|
||||
listItem.setInfo( "Video", infoLabels=details )
|
||||
|
||||
people = API().getPeople(result)
|
||||
|
||||
# Process Genres
|
||||
genre = API().getGenre(result)
|
||||
|
||||
listItem.setInfo('video', {'director' : people.get('Director')})
|
||||
listItem.setInfo('video', {'writer' : people.get('Writer')})
|
||||
listItem.setInfo('video', {'mpaa': result.get("OfficialRating")})
|
||||
listItem.setInfo('video', {'genre': genre})
|
313
resources/lib/Player.py
Normal file
313
resources/lib/Player.py
Normal file
|
@ -0,0 +1,313 @@
|
|||
import xbmcaddon
|
||||
import xbmcplugin
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import os
|
||||
import threading
|
||||
import json
|
||||
import KodiMonitor
|
||||
import Utils as utils
|
||||
from DownloadUtils import DownloadUtils
|
||||
from PlayUtils import PlayUtils
|
||||
from ClientInformation import ClientInformation
|
||||
from LibrarySync import LibrarySync
|
||||
librarySync = LibrarySync()
|
||||
|
||||
|
||||
# service class for playback monitoring
|
||||
class Player( xbmc.Player ):
|
||||
|
||||
logLevel = 0
|
||||
played_information = {}
|
||||
downloadUtils = None
|
||||
settings = None
|
||||
playStats = {}
|
||||
|
||||
def __init__( self, *args ):
|
||||
|
||||
self.settings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
self.downloadUtils = DownloadUtils()
|
||||
try:
|
||||
self.logLevel = int(self.settings.getSetting('logLevel'))
|
||||
except:
|
||||
pass
|
||||
self.printDebug("mb3sync Service -> starting playback monitor service")
|
||||
self.played_information = {}
|
||||
pass
|
||||
|
||||
def printDebug(self, msg, level = 1):
|
||||
if(self.logLevel >= level):
|
||||
if(self.logLevel == 2):
|
||||
try:
|
||||
xbmc.log("mb3sync " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg))
|
||||
except UnicodeEncodeError:
|
||||
xbmc.log("mb3sync " + str(level) + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
|
||||
else:
|
||||
try:
|
||||
xbmc.log("mb3sync " + str(level) + " -> " + str(msg))
|
||||
except UnicodeEncodeError:
|
||||
xbmc.log("mb3sync " + str(level) + " -> " + str(msg.encode('utf-8')))
|
||||
|
||||
def deleteItem (self, url):
|
||||
return_value = xbmcgui.Dialog().yesno(__language__(30091),__language__(30092))
|
||||
if return_value:
|
||||
self.printDebug('Deleting via URL: ' + url)
|
||||
progress = xbmcgui.DialogProgress()
|
||||
progress.create(__language__(30052), __language__(30053))
|
||||
self.downloadUtils.downloadUrl(url, type="DELETE")
|
||||
progress.close()
|
||||
xbmc.executebuiltin("Container.Refresh")
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def hasData(self, data):
|
||||
if(data == None or len(data) == 0 or data == "None"):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def stopAll(self):
|
||||
|
||||
if(len(self.played_information) == 0):
|
||||
return
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
self.printDebug("mb3sync Service -> played_information : " + str(self.played_information))
|
||||
|
||||
for item_url in self.played_information:
|
||||
data = self.played_information.get(item_url)
|
||||
|
||||
if(data != None):
|
||||
self.printDebug("mb3sync Service -> item_url : " + item_url)
|
||||
self.printDebug("mb3sync Service -> item_data : " + str(data))
|
||||
|
||||
deleteurl = data.get("deleteurl")
|
||||
runtime = data.get("runtime")
|
||||
currentPosition = data.get("currentPosition")
|
||||
item_id = data.get("item_id")
|
||||
refresh_id = data.get("refresh_id")
|
||||
currentFile = data.get("currentfile")
|
||||
|
||||
if(refresh_id != None):
|
||||
#todo: trigger update of single item from MB3, for now trigger full playcounts update
|
||||
librarySync.updatePlayCounts()
|
||||
|
||||
if(currentPosition != None and self.hasData(runtime)):
|
||||
runtimeTicks = int(runtime)
|
||||
self.printDebug("mb3sync Service -> runtimeticks:" + str(runtimeTicks))
|
||||
percentComplete = (currentPosition * 10000000) / runtimeTicks
|
||||
markPlayedAt = float(90) / 100
|
||||
|
||||
self.printDebug("mb3sync Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt))
|
||||
self.stopPlayback(data)
|
||||
|
||||
if (percentComplete > markPlayedAt):
|
||||
gotDeleted = 0
|
||||
if(deleteurl != None and deleteurl != ""):
|
||||
self.printDebug("mb3sync Service -> Offering Delete:" + str(deleteurl))
|
||||
gotDeleted = self.deleteItem(deleteurl)
|
||||
|
||||
|
||||
self.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
|
||||
self.downloadUtils.downloadUrl(url, type="DELETE")
|
||||
|
||||
def stopPlayback(self, data):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
|
||||
item_id = data.get("item_id")
|
||||
audioindex = data.get("AudioStreamIndex")
|
||||
subtitleindex = data.get("SubtitleStreamIndex")
|
||||
playMethod = data.get("playmethod")
|
||||
currentPosition = data.get("currentPosition")
|
||||
positionTicks = str(int(currentPosition * 10000000))
|
||||
|
||||
url = ("http://%s:%s/mediabrowser/Sessions/Playing/Stopped" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port')))
|
||||
|
||||
url = url + "?itemId=" + item_id
|
||||
|
||||
url = url + "&canSeek=true"
|
||||
url = url + "&PlayMethod=" + playMethod
|
||||
url = url + "&QueueableMediaTypes=Video"
|
||||
url = url + "&MediaSourceId=" + item_id
|
||||
url = url + "&PositionTicks=" + positionTicks
|
||||
if(audioindex != None and audioindex!=""):
|
||||
url = url + "&AudioStreamIndex=" + audioindex
|
||||
|
||||
if(subtitleindex != None and subtitleindex!=""):
|
||||
url = url + "&SubtitleStreamIndex=" + subtitleindex
|
||||
|
||||
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
|
||||
|
||||
|
||||
def reportPlayback(self):
|
||||
self.printDebug("reportPlayback Called")
|
||||
|
||||
currentFile = xbmc.Player().getPlayingFile()
|
||||
|
||||
#TODO need to change this to use the one in the data map
|
||||
playTime = xbmc.Player().getTime()
|
||||
|
||||
data = self.played_information.get(currentFile)
|
||||
|
||||
# only report playback if mb3sync has initiated the playback (item_id has value)
|
||||
if(data != None and data.get("item_id") != None):
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
|
||||
item_id = data.get("item_id")
|
||||
audioindex = data.get("AudioStreamIndex")
|
||||
subtitleindex = data.get("SubtitleStreamIndex")
|
||||
playMethod = data.get("playmethod")
|
||||
paused = data.get("paused")
|
||||
|
||||
url = ("http://%s:%s/mediabrowser/Sessions/Playing/Progress" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port')))
|
||||
|
||||
url = url + "?itemId=" + item_id
|
||||
|
||||
url = url + "&canSeek=true"
|
||||
url = url + "&PlayMethod=" + playMethod
|
||||
url = url + "&QueueableMediaTypes=Video"
|
||||
url = url + "&MediaSourceId=" + item_id
|
||||
|
||||
url = url + "&PositionTicks=" + str(int(playTime * 10000000))
|
||||
|
||||
if(audioindex != None and audioindex!=""):
|
||||
url = url + "&AudioStreamIndex=" + audioindex
|
||||
|
||||
if(subtitleindex != None and subtitleindex!=""):
|
||||
url = url + "&SubtitleStreamIndex=" + subtitleindex
|
||||
|
||||
if(paused == None):
|
||||
paused = "false"
|
||||
url = url + "&IsPaused=" + paused
|
||||
|
||||
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
|
||||
|
||||
def onPlayBackPaused( self ):
|
||||
currentFile = xbmc.Player().getPlayingFile()
|
||||
self.printDebug("PLAYBACK_PAUSED : " + currentFile)
|
||||
if(self.played_information.get(currentFile) != None):
|
||||
self.played_information[currentFile]["paused"] = "true"
|
||||
self.reportPlayback()
|
||||
|
||||
def onPlayBackResumed( self ):
|
||||
currentFile = xbmc.Player().getPlayingFile()
|
||||
self.printDebug("PLAYBACK_RESUMED : " + currentFile)
|
||||
if(self.played_information.get(currentFile) != None):
|
||||
self.played_information[currentFile]["paused"] = "false"
|
||||
self.reportPlayback()
|
||||
|
||||
def onPlayBackSeek( self, time, seekOffset ):
|
||||
self.printDebug("PLAYBACK_SEEK")
|
||||
self.reportPlayback()
|
||||
|
||||
def onPlayBackStarted( self ):
|
||||
# Will be called when xbmc starts playing a file
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
self.stopAll()
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
|
||||
if xbmc.Player().isPlaying():
|
||||
currentFile = xbmc.Player().getPlayingFile()
|
||||
self.printDebug("mb3sync Service -> onPlayBackStarted" + currentFile)
|
||||
|
||||
# grab all the info about this item from the stored windows props
|
||||
# only ever use the win props here, use the data map in all other places
|
||||
deleteurl = WINDOW.getProperty(currentFile + "deleteurl")
|
||||
runtime = WINDOW.getProperty(currentFile + "runtimeticks")
|
||||
item_id = WINDOW.getProperty(currentFile + "item_id")
|
||||
refresh_id = WINDOW.getProperty(currentFile + "refresh_id")
|
||||
audioindex = WINDOW.getProperty(currentFile + "AudioStreamIndex")
|
||||
subtitleindex = WINDOW.getProperty(currentFile + "SubtitleStreamIndex")
|
||||
playMethod = WINDOW.getProperty(currentFile + "playmethod")
|
||||
itemType = WINDOW.getProperty(currentFile + "type")
|
||||
seekTime = WINDOW.getProperty(currentFile + "seektime")
|
||||
if seekTime != "":
|
||||
self.seekToPosition(int(seekTime))
|
||||
|
||||
if(item_id == None or len(item_id) == 0):
|
||||
return
|
||||
|
||||
url = ("http://%s:%s/mediabrowser/Sessions/Playing" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port')))
|
||||
|
||||
url = url + "?itemId=" + item_id
|
||||
|
||||
url = url + "&canSeek=true"
|
||||
url = url + "&PlayMethod=" + playMethod
|
||||
url = url + "&QueueableMediaTypes=Video"
|
||||
url = url + "&MediaSourceId=" + item_id
|
||||
|
||||
if(audioindex != None and audioindex!=""):
|
||||
url = url + "&AudioStreamIndex=" + audioindex
|
||||
|
||||
if(subtitleindex != None and subtitleindex!=""):
|
||||
url = url + "&SubtitleStreamIndex=" + subtitleindex
|
||||
|
||||
self.downloadUtils.downloadUrl(url, postBody="", type="POST")
|
||||
|
||||
# save data map for updates and position calls
|
||||
data = {}
|
||||
data["deleteurl"] = deleteurl
|
||||
data["runtime"] = runtime
|
||||
data["item_id"] = item_id
|
||||
data["refresh_id"] = refresh_id
|
||||
data["currentfile"] = currentFile
|
||||
data["AudioStreamIndex"] = audioindex
|
||||
data["SubtitleStreamIndex"] = subtitleindex
|
||||
data["playmethod"] = playMethod
|
||||
data["Type"] = itemType
|
||||
self.played_information[currentFile] = data
|
||||
|
||||
self.printDebug("mb3sync Service -> ADDING_FILE : " + currentFile)
|
||||
self.printDebug("mb3sync Service -> ADDING_FILE : " + str(self.played_information))
|
||||
|
||||
# log some playback stats
|
||||
if(itemType != None):
|
||||
if(self.playStats.get(itemType) != None):
|
||||
count = self.playStats.get(itemType) + 1
|
||||
self.playStats[itemType] = count
|
||||
else:
|
||||
self.playStats[itemType] = 1
|
||||
|
||||
if(playMethod != None):
|
||||
if(self.playStats.get(playMethod) != None):
|
||||
count = self.playStats.get(playMethod) + 1
|
||||
self.playStats[playMethod] = count
|
||||
else:
|
||||
self.playStats[playMethod] = 1
|
||||
|
||||
# reset in progress position
|
||||
self.reportPlayback()
|
||||
|
||||
def GetPlayStats(self):
|
||||
return self.playStats
|
||||
|
||||
def onPlayBackEnded( self ):
|
||||
# Will be called when xbmc stops playing a file
|
||||
self.printDebug("mb3sync Service -> onPlayBackEnded")
|
||||
self.stopAll()
|
||||
|
||||
def onPlayBackStopped( self ):
|
||||
# Will be called when user stops xbmc playing a file
|
||||
self.printDebug("mb3sync Service -> onPlayBackStopped")
|
||||
self.stopAll()
|
||||
|
||||
def seekToPosition(self, seekTo):
|
||||
|
||||
#Jump to resume point
|
||||
jumpBackSec = 10
|
||||
seekToTime = seekTo - jumpBackSec
|
||||
count = 0
|
||||
while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times
|
||||
count = count + 1
|
||||
xbmc.Player().pause
|
||||
xbmc.sleep(100)
|
||||
xbmc.Player().seekTime(seekToTime)
|
||||
xbmc.sleep(100)
|
||||
xbmc.Player().play()
|
162
resources/lib/Utils.py
Normal file
162
resources/lib/Utils.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
#################################################################################################
|
||||
# utils
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
import json
|
||||
import os
|
||||
import inspect
|
||||
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
|
||||
from xml.etree import ElementTree
|
||||
from xml.dom import minidom
|
||||
import xml.etree.cElementTree as ET
|
||||
|
||||
from API import API
|
||||
from PlayUtils import PlayUtils
|
||||
from DownloadUtils import DownloadUtils
|
||||
downloadUtils = DownloadUtils()
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
language = addonSettings.getLocalizedString
|
||||
|
||||
def logMsg(title, msg, level = 1):
|
||||
|
||||
#todo --> get this from a setting
|
||||
logLevel = 0
|
||||
|
||||
if(logLevel >= level):
|
||||
if(logLevel == 1):
|
||||
try:
|
||||
xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg))
|
||||
except UnicodeEncodeError:
|
||||
xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
|
||||
else:
|
||||
try:
|
||||
xbmc.log(title + " -> " + str(msg))
|
||||
except UnicodeEncodeError:
|
||||
xbmc.log(title + " -> " + str(msg.encode('utf-8')))
|
||||
|
||||
|
||||
def checkKodiSources():
|
||||
print "All sources in Kodi -->"
|
||||
addon = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
addondir = xbmc.translatePath( addon.getAddonInfo('profile') )
|
||||
|
||||
dataPath = os.path.join(addondir,"library")
|
||||
movieLibrary = os.path.join(dataPath,'movies')
|
||||
tvLibrary = os.path.join(dataPath,'tvshows')
|
||||
|
||||
if not xbmcvfs.exists(dataPath):
|
||||
xbmcvfs.mkdir(dataPath)
|
||||
if not xbmcvfs.exists(movieLibrary):
|
||||
xbmcvfs.mkdir(movieLibrary)
|
||||
if not xbmcvfs.exists(tvLibrary):
|
||||
xbmcvfs.mkdir(tvLibrary)
|
||||
|
||||
allKodiSources = list()
|
||||
|
||||
json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Files.GetSources", "params": { "media": "video"}, "id": 1 }')
|
||||
jsonobject = json.loads(json_response.decode('utf-8','replace'))
|
||||
|
||||
if(jsonobject.has_key('result')):
|
||||
result = jsonobject['result']
|
||||
if(result.has_key('sources')):
|
||||
for source in result["sources"]:
|
||||
allKodiSources.append(source["label"])
|
||||
|
||||
allKodiSources = set(allKodiSources)
|
||||
|
||||
rebootRequired = False
|
||||
if not "mediabrowser_movies" in allKodiSources:
|
||||
addKodiSource("mediabrowser_movies",movieLibrary)
|
||||
rebootRequired = True
|
||||
if not "mediabrowser_tvshows" in allKodiSources:
|
||||
addKodiSource("mediabrowser_tvshows",tvLibrary)
|
||||
rebootRequired = True
|
||||
|
||||
if rebootRequired:
|
||||
ret = xbmcgui.Dialog().yesno(heading="MediaBrowser Sync service", line1="A restart of Kodi is needed to apply changes. Do you want to reboot now ?")
|
||||
if ret:
|
||||
xbmc.executebuiltin("RestartApp")
|
||||
|
||||
def addKodiSource(name, path):
|
||||
userDataPath = xbmc.translatePath( "special://profile" )
|
||||
sourcesFile = os.path.join(userDataPath,'sources.xml')
|
||||
|
||||
print "####parsing sources file #####" + sourcesFile
|
||||
|
||||
tree = ET.ElementTree(file=sourcesFile)
|
||||
root = tree.getroot()
|
||||
|
||||
videosources = root.find("video")
|
||||
|
||||
#remove any existing entries
|
||||
allsources = videosources.findall("source")
|
||||
if allsources != None:
|
||||
for source in allsources:
|
||||
if source.find("name").text == name:
|
||||
videosources.remove(source)
|
||||
|
||||
# add new source
|
||||
source = SubElement(videosources,'source')
|
||||
SubElement(source, "name").text = name
|
||||
SubElement(source, "path").text = path
|
||||
|
||||
tree.write(sourcesFile)
|
||||
|
||||
def checkAuthentication():
|
||||
#check authentication
|
||||
if addonSettings.getSetting('username') != "" and addonSettings.getSetting('ipaddress') != "":
|
||||
try:
|
||||
downloadUtils.authenticate()
|
||||
except Exception, e:
|
||||
logMsg("MB3 Syncer authentication failed",e)
|
||||
pass
|
||||
|
||||
def prettifyXml(elem):
|
||||
rough_string = etree.tostring(elem, "utf-8")
|
||||
reparsed = minidom.parseString(rough_string)
|
||||
return reparsed.toprettyxml(indent="\t")
|
||||
|
||||
def doKodiCleanup():
|
||||
#remove old testdata and remove missing files
|
||||
json_response = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": {"properties" : ["file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMovies"}')
|
||||
jsonobject = json.loads(json_response.decode('utf-8','replace'))
|
||||
if(jsonobject.has_key('result')):
|
||||
result = jsonobject['result']
|
||||
if(result.has_key('movies')):
|
||||
movies = result['movies']
|
||||
for movie in movies:
|
||||
if (xbmcvfs.exists(movie["file"]) == False) or ("plugin.video.xbmb3c" in movie["file"]):
|
||||
print "deleting --> " + movie["file"]
|
||||
xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.RemoveMovie", "params": { "movieid": %i}, "id": 1 }' %(movie["movieid"]))
|
||||
|
||||
|
||||
def get_params( paramstring ):
|
||||
xbmc.log("Parameter string: " + paramstring)
|
||||
param={}
|
||||
if len(paramstring)>=2:
|
||||
params=paramstring
|
||||
|
||||
if params[0] == "?":
|
||||
cleanedparams=params[1:]
|
||||
else:
|
||||
cleanedparams=params
|
||||
|
||||
if (params[len(params)-1]=='/'):
|
||||
params=params[0:len(params)-2]
|
||||
|
||||
pairsofparams=cleanedparams.split('&')
|
||||
for i in range(len(pairsofparams)):
|
||||
splitparams={}
|
||||
splitparams=pairsofparams[i].split('=')
|
||||
if (len(splitparams))==2:
|
||||
param[splitparams[0]]=splitparams[1]
|
||||
elif (len(splitparams))==3:
|
||||
param[splitparams[0]]=splitparams[1]+"="+splitparams[2]
|
||||
xbmc.log("XBMB3C -> Detected parameters: " + str(param))
|
||||
return param
|
||||
|
||||
|
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.
|
BIN
resources/mb3.png
Normal file
BIN
resources/mb3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
17
resources/settings.xml
Normal file
17
resources/settings.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<settings>
|
||||
<category label="30014"> <!-- MediaBrowser -->
|
||||
<setting id="ipaddress" type="text" label="30000" default="" 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="30022"> <!-- Advanced -->
|
||||
<setting id="logLevel" type="enum" label="30004" values="None|Info|Debug" default="0" />
|
||||
<setting id="profile" type="bool" label="30010" default="false" visible="true" enable="true" />
|
||||
<setting id="reportMetrics" type="bool" label="30224" default="true" visible="true" enable="true" />
|
||||
<setting id="skinMessageIgnored" type="bool" label="30233" default="false" visible="true" enable="true" />
|
||||
</category>
|
||||
</settings>
|
101
service.py
Normal file
101
service.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
import xbmcaddon
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import os
|
||||
import threading
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
addonSettings = xbmcaddon.Addon(id='plugin.video.mb3sync')
|
||||
cwd = addonSettings.getAddonInfo('path')
|
||||
BASE_RESOURCE_PATH = xbmc.translatePath( os.path.join( cwd, 'resources', 'lib' ) )
|
||||
sys.path.append(BASE_RESOURCE_PATH)
|
||||
|
||||
WINDOW = xbmcgui.Window( 10000 )
|
||||
|
||||
import KodiMonitor
|
||||
import Utils as utils
|
||||
from LibrarySync import LibrarySync
|
||||
from Player import Player
|
||||
librarySync = LibrarySync()
|
||||
|
||||
class Service():
|
||||
|
||||
|
||||
def __init__(self, *args ):
|
||||
self.KodiMonitor = KodiMonitor.Kodi_Monitor()
|
||||
|
||||
utils.logMsg("MB3 Sync Service" "starting Monitor",0)
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ServiceEntryPoint(self):
|
||||
|
||||
player = Player()
|
||||
lastProgressUpdate = datetime.today()
|
||||
|
||||
#perform kodi cleanup (needed while testing, can be removed later if needed)
|
||||
utils.doKodiCleanup()
|
||||
|
||||
# check kodi library sources
|
||||
utils.checkKodiSources()
|
||||
|
||||
interval_FullSync = 120
|
||||
interval_IncrementalSync = 30
|
||||
|
||||
cur_seconds_fullsync = 0
|
||||
cur_seconds_incrsync = 0
|
||||
|
||||
while not xbmc.abortRequested:
|
||||
|
||||
xbmc.sleep(1000)
|
||||
|
||||
if xbmc.Player().isPlaying():
|
||||
try:
|
||||
playTime = xbmc.Player().getTime()
|
||||
currentFile = xbmc.Player().getPlayingFile()
|
||||
|
||||
if(player.played_information.get(currentFile) != None):
|
||||
player.played_information[currentFile]["currentPossition"] = playTime
|
||||
|
||||
# send update
|
||||
td = datetime.today() - lastProgressUpdate
|
||||
secDiff = td.seconds
|
||||
if(secDiff > 10):
|
||||
try:
|
||||
player.reportPlayback()
|
||||
except Exception, msg:
|
||||
xbmc.log("MB3 Sync Service -> Exception reporting progress : " + msg)
|
||||
pass
|
||||
lastProgressUpdate = datetime.today()
|
||||
|
||||
except Exception, e:
|
||||
xbmc.log("MB3 Sync Service -> Exception in Playback Monitor Service : " + str(e))
|
||||
pass
|
||||
else:
|
||||
# background worker for database sync
|
||||
if WINDOW.getProperty("mb3_authenticated") == "true":
|
||||
|
||||
#full sync
|
||||
if((interval_FullSync >= cur_seconds_fullsync)):
|
||||
librarySync.syncDatabase()
|
||||
cur_seconds_fullsync = interval_FullSync
|
||||
else:
|
||||
cur_seconds_fullsync -= 1
|
||||
|
||||
#incremental sync
|
||||
if((interval_IncrementalSync >= cur_seconds_incrsync)):
|
||||
librarySync.updatePlayCounts()
|
||||
cur_seconds_incrsync = interval_IncrementalSync
|
||||
else:
|
||||
cur_seconds_incrsync -= 1
|
||||
else:
|
||||
utils.checkAuthentication()
|
||||
|
||||
utils.logMsg("MB3 Sync Service" "stopping Service",0)
|
||||
|
||||
|
||||
#start the service
|
||||
Service().ServiceEntryPoint()
|
Loading…
Reference in a new issue