diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..1c9b0bde
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,283 @@
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+-------------------------------------------------------------------------
+-------------------------------------------------------------------------
diff --git a/addon.xml b/addon.xml
new file mode 100644
index 00000000..6cb4f7ae
--- /dev/null
+++ b/addon.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ executable video audio image
+
+
+
+
+ all
+ en
+ GNU GENERAL PUBLIC LICENSE. Version 2, June 1991
+
+ http://mediabrowser.tv/
+
+
+
+
+
diff --git a/changelog.txt b/changelog.txt
new file mode 100644
index 00000000..b5806857
--- /dev/null
+++ b/changelog.txt
@@ -0,0 +1,2 @@
+0.0.1
+- initital alpha version
\ No newline at end of file
diff --git a/default.py b/default.py
new file mode 100644
index 00000000..73e54bc9
--- /dev/null
+++ b/default.py
@@ -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)
+
diff --git a/fanart.jpg b/fanart.jpg
new file mode 100644
index 00000000..77b142fe
Binary files /dev/null and b/fanart.jpg differ
diff --git a/icon.png b/icon.png
new file mode 100644
index 00000000..e10aa896
Binary files /dev/null and b/icon.png differ
diff --git a/resources/__init__.py b/resources/__init__.py
new file mode 100644
index 00000000..b93054b3
--- /dev/null
+++ b/resources/__init__.py
@@ -0,0 +1 @@
+# Dummy file to make this directory a package.
diff --git a/resources/language/Dutch/strings.xml b/resources/language/Dutch/strings.xml
new file mode 100644
index 00000000..a2630216
--- /dev/null
+++ b/resources/language/Dutch/strings.xml
@@ -0,0 +1,239 @@
+
+
+ Primaire server adres:
+ Enkele mappen automatisch openen:
+ Afspelen van stream ipv SMB:
+ Log niveau:
+ Gebruikersnaam:
+ Wachtwoord:
+ Samba gebruikersnaam:
+ Samba wachtwoord:
+ Transcode:
+ Prestatie profilering inschakelen:
+ Lokale cache systeem
+
+ MediaBrowser
+ Netwerk
+ Apparaatnaam
+
+ Geavanceerd
+ Gebruikersnaam:
+ Wachtwoord:
+ Gebruik SIMPLEJSON ipv JSON
+
+ Poortnummer:
+ Aantal recente films die getoond worden:
+ Aantal recente TV-series die getoond worden:
+ Aantal recente muziekalbums die getoond worden:
+ Markeer als bekeken bij starten van afspelen:
+ Gebruik seizoen poster bij afleveringen
+
+ Genre filter ...
+ Speel alles vanaf hier
+ Vernieuwen
+ Wissen
+ Voeg film toe aan CouchPotato
+
+ Ongeldige gebruikersnaam/wachtwoord
+ Gebruikersnaam niet gevonden
+
+ Wissen...
+ Wacht op server voor wissen
+
+ Server standaard
+ Titel
+ Jaar
+ Premiere datum
+ Datum toegevoegd
+ Critici beoordeling
+ Community beoordeling
+ Aantal keer bekeken
+ Budget
+
+ Sorteer op
+
+ Geen
+ Actie
+ Avontuur
+ Animatie
+ Misdaad
+ Comedy
+ Documentaire
+ Drama
+ Fantasie
+ Nederlands
+ Historie
+ Horror
+ Muziek
+ Musical
+ Mysterie
+ Romantiek
+ Science Fiction
+ Kort
+ Spanning
+ Thriller
+ Western
+
+ Genre filter
+ Bevestig wissen
+ Dit item wissen ? Dit zal het bestand volledig verwijderen.
+
+ Markeer als bekeken
+ Mark als onbekeken
+ Voeg toe aan favorieten
+ Verwijder uit favorieten
+ Sorteer op...
+ Sorteer oplopend
+ Sorteer aflopend
+ Toon acteurs
+
+
+ Hervatten
+ Hervatten vanaf
+ Start vanaf begin
+
+ Interface
+ Inclusief stream info
+ Inclusief personen
+ Inclusief filminfo
+ Bij hervatten aantal seconden terugspringen
+ Markeer als bekeken wanneer na percentage gestopt
+ Inclusief aantal en afspeel tellers
+ - Achtergrond plaatjes verversen (secondes)
+ Inclusief hervat-percentage
+ Afleveringnummer tonen in titel
+ Toon voortgang
+ Laden van content
+ Retrieving Data
+ Parsing Jason Data
+ Downloading Jason Data
+ Done
+ Processing Item :
+ Toon wismogelijkheid na bekijken van aflevering
+ Afspeelfout!
+ Dit item kan niet worden afgespeeld
+ Lockaal pad gedetecteerd
+ De MB3 bibliotheek bevat lokale paden. U moet UNC-paden gebruiken of afspelen van stream inschakelen. Pad:
+ Waarschuwing
+ Debug logging ingeschakeld.
+ Dit heeft effect op de performance.
+ Fout
+ XBMB3C service werkt niet
+ Herstart XBMC aub
+ Zoeken
+
+ Schakel Themamuziek in (vereist herstart)
+ - Herhalen van themamuziek
+ Activeer achtergrondafbeelding (vereist herstart)
+ Services
+ Activeer Info Loader (vereist herstart)
+ Activeer Menu Loader (vereist herstart)
+ Activeer WebSocket Remote (vereist herstart)
+ Activeer In Progress Loader (vereist herstart)
+ Activeer Recent Info Loader (vereist herstart)
+ Activeer Random Loader (vereist herstart)
+ Activeer Next Up Loader (vereist herstart)
+
+ Skin ondersteund het vastleggen van views niet
+ Selecteer item actie (vereist herstart)
+
+ Toon indactors
+ - Toon bekeken indator
+ - Toon aantal onbekeken indicator
+ - Toon afspeel-percentage indicator
+ Sorteer volgende (NextUp) op titel
+ Deactiveer speciale afbeeldingen (bv CoverArt)
+ Metadata
+ Afbeeldingen
+ Video kwaliteit
+
+ Activeer Suggested Loader (vereist herstart)
+ Seizoen tonen in titel
+ Seizoenen verbergen
+
+ Direct Play - HTTP
+ Direct Play
+ Transcoding
+ Server Detection Succeeded
+ Found server
+ Address :
+
+
+ Alle films
+ Alle TV
+ Alle Muziek
+ Kanalen
+ Recent toegevoegde films
+ Recent toegevoegde afleveringen
+ Recent toegevoegde albums
+ Niet afgekeken films
+ Niet afgekeken afleveringen
+ NextUp afleveringen
+ Favoriete films
+ Favoriete TV-series
+ Favoriete afleveringen
+ Vaak afgespeelde albums
+ Upcoming TV
+ BoxSets
+ Trailers
+ Muziek videos
+ Fotos
+ Onbekeken films
+ Film Genres
+ Film Studios
+ Film Acteurs
+ Onbekeken afleveringen
+ TV Genres
+ TV Networks
+ TV Acteurs
+ Afspeellijsten
+ Zoeken
+ Views instellen
+
+ Selecteer gebruiker
+ Profilering ingeschakeld.
+ Svp onthouden om weer uit te schakelen na het testen.
+ Error in ArtworkRotationThread
+ Kan niet verbinden met server
+ Error in LoadMenuOptionsThread
+
+ Activeer Playlists Loader (vereist herstart)
+
+ Liedjes
+ Albums
+ Album artiesten
+ Artiesten
+ Muziek Genres
+
+ Schakel Themavideos in (vereist herstart)
+ - Herhalen van themavideos
+
+ Schakel het forceren van view uit
+ Schakel snelle modus in (beta)
+ Automatisch resterende afleveringen in een seizoen afspelen
+ Boxsets tonen in de overzichten (vereist herstart)
+ Afbeeldingen comprimeren
+ Activeer Skin Helper (vereust herstart)
+ Laatste
+ Bezig
+ Volgende
+ Gebruikerweergaven
+
+
+ Actief
+ Herstel standaard
+ Films
+ BoxSets
+ Trailers
+ Series
+ Seizoenen
+ Afleveringen
+ Muziek - artiesten
+ Muziek - albums
+ Muziekvideos
+ Muziek - liedjes
+ Kanalen
+
+
+
+
\ No newline at end of file
diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml
new file mode 100644
index 00000000..00814d50
--- /dev/null
+++ b/resources/language/English/strings.xml
@@ -0,0 +1,252 @@
+
+
+ Primary Server Address
+ Auto enter single folder items:
+ Play from HTTP instead of SMB:
+ Log Level:
+ Username:
+ Password:
+ Network Username:
+ Network Password:
+ Transcode:
+ Enable Performance Profiling
+ Local caching system
+
+ MediaBrowser
+ Network
+ Device Name
+
+ Advanced
+ Username:
+ Password:
+ Use SIMPLEJSON instead of JSON
+
+ Port Number:
+ Number of recent Movies to show:
+ Number of recent TV episodes to show:
+ Number of recent Music Albums to show:
+ Mark watched at start of playback:
+ Set Season poster for episodes
+
+ Genre Filter ...
+ Play All from Here
+ Refresh
+ Delete
+ Add Movie to CouchPotato
+
+ Incorrect Username/Password
+ Username not found
+
+ Deleting
+ Waiting for server to delete
+
+ Server Default
+ Title
+ Year
+ Premiere Date
+ Date Created
+ Critic Rating
+ Community Rating
+ Play Count
+ Budget
+
+
+ Sort By
+
+ None
+ Action
+ Adventure
+ Animation
+ Crime
+ Comedy
+ Documentary
+ Drama
+ Fantasy
+ Foreign
+ History
+ Horror
+ Music
+ Musical
+ Mystery
+ Romance
+ Science Fiction
+ Short
+ Suspense
+ Thriller
+ Western
+
+ Genre Filter
+ Confirm file delete?
+ Delete this item? This action will delete media and associated data files.
+
+ Mark Watched
+ Mark Unwatched
+ Add to Favorites
+ Remove from Favorites
+ Sort By ...
+ Sort Order Descending
+ Sort Order Ascending
+ Show People
+
+
+ Resume
+ Resume from
+ Start from beginning
+
+ Interface
+ Include Stream Info
+ Include People
+ Include Overview
+ On Resume Jump Back Seconds
+ - Offer delete when stopped above %
+ Add Item and Played Counts
+ Background Art Refresh Rate (seconds)
+ Add Resume Percent
+ Add Episode Number
+ Show Load Progress
+ Loading Content
+ Retrieving Data
+ Parsing Jason Data
+ Downloading Jason Data
+ Done
+ Processing Item :
+ Offer delete for watched episodes
+ Play Error
+ This item is not playable
+ Local path detected
+ Your MB3 Server contains local paths. Please change server paths to UNC or change XBMB3C setting 'Play from Stream' to true. Path:
+ Warning
+ Debug logging enabled.
+ This will affect performance.
+ Error
+ Monitoring service is not running
+ If you have just installed please restart Kodi
+ Search
+
+ Enable Theme Music (Requires Restart)
+ - Loop Theme Music
+ Enable Background Image (Requires Restart)
+ Services
+ Enable Info Loader (Requires Restart)
+ Enable Menu Loader (Requires Restart)
+ Enable WebSocket Remote (Requires Restart)
+ Enable In Progress Loader (Requires Restart)
+ Enable Recent Info Loader (Requires Restart)
+ Enable Random Loader (Requires Restart)
+ Enable Next Up Loader (Requires Restart)
+
+ Skin does not support setting views
+ Select item action (Requires Restart)
+
+ Show Indicators
+ - Show Watched Indicator
+ - Show Unplayed Count Indicator
+ - Show Played Percentage Indicator
+ Sort NextUp by Show Title
+ Disable Enhanced Images (eg CoverArt)
+ Metadata
+ Artwork
+ Video Quality
+
+ Enable Suggested Loader (Requires Restart)
+ Add Season Number
+ Flatten Seasons
+
+ Direct Play - HTTP
+ Direct Play
+ Transcoding
+ Server Detection Succeeded
+ Found server
+ Address :
+
+
+ All Movies
+ All TV
+ All Music
+ Channels
+ Recently Added Movies
+ Recently Added Episodes
+ Recently Added Albums
+ In Progress Movies
+ In Progress Episodes
+ Next Episodes
+ Favorite Movies
+ Favorite Shows
+ Favorite Episodes
+ Frequent Played Albums
+ Upcoming TV
+ BoxSets
+ Trailers
+ Music Videos
+ Photos
+ Unwatched Movies
+ Movie Genres
+ Movie Studios
+ Movie Actors
+ Unwatched Episodes
+ TV Genres
+ TV Networks
+ TV Actors
+ Playlists
+ Search
+ Set Views
+
+ Select User
+ Profiling enabled.
+ Please remember to turn off when finished testing.
+ Error in ArtworkRotationThread
+ Unable to connect to server
+ Error in LoadMenuOptionsThread
+
+ Enable Playlists Loader (Requires Restart)
+
+ Songs
+ Albums
+ Album Artists
+ Artists
+ Music Genres
+
+ Enable Theme Videos (Requires Restart)
+ - Loop Theme Videos
+
+ Disable forced view
+ Enable Fast Processing
+ AutoPlay remaining episodes in a season
+ Show boxsets collapsed in views (Requires Restart)
+ Compress Artwork
+ Enable Skin Helper (Requires Restart)
+ Latest
+ In Progress
+ NextUp
+ User Views
+ Report Metrics
+ Use Kodi Sorting
+ Runtime
+
+ Random Movies
+ Random Episodes
+
+ Skin Compatibility Warning
+ Your current skin is not fully compatible.
+ For a better experience use a skin from the forum.
+ http://tinyurl.com/knfus2x
+ Don't Show Skin Compatibility Message
+ Add Show Name (Season + Episode)
+
+
+ Active
+ Clear Settings
+ Movies
+ BoxSets
+ Trailers
+ Series
+ Seasons
+ Episodes
+ Music Artists
+ Music Albums
+ Music Videos
+ Music Tracks
+ Channels
+
+
+
diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml
new file mode 100644
index 00000000..1168bb8d
--- /dev/null
+++ b/resources/language/German/strings.xml
@@ -0,0 +1,227 @@
+
+
+ IP-Adresse des Servers
+ Automatisches Öffnen von Ordnern mit einem Eintrag
+ Via HTTP abspielen statt SMB/NFS:
+ Log Level:
+ Benutzername:
+ Passwort:
+ Samba-Benutzername:
+ Samba-Passwort:
+ Transkodieren:
+ Performancemessung aktivieren
+ Caching-Mechanismus
+
+ MediaBrowser
+ Netzwerk
+ Gerätename
+
+ Erweitert
+ Benutzername:
+ Passwort:
+
+ Portnummer:
+ Anzahl der zuletzt hinzugefügten Filme:
+ Anzahl der zuletzt hinzugefügten Episoden:
+ Anzahl der zuletzt hinzugefügten Alben:
+ Bei Start der Wiedergabe als 'gesehen' markieren:
+ Staffelposter für Episoden nutzen
+
+ Genre Filter ...
+ Alles von hier abspielen
+ Aktualisieren
+ Löschen
+ Film zu CouchPotato hinzufügen
+
+ Benutzername/Passwort falsch
+ Benutzername nicht gefunden
+
+ Lösche
+ Lösche von Server
+
+ Server Standard
+ Titel
+ Jahr
+ Premierendatum
+ Datum hinzugefügt
+ Bewertung
+ Zuschauerbewertung
+ Abspielzähler
+ Budget
+
+ Sortiere nach
+
+ Kein Filter
+ Action
+ Adventure
+ Animation
+ Crime
+ Comedy
+ Documentary
+ Drama
+ Fantasy
+ Foreign
+ History
+ Horror
+ Music
+ Musical
+ Mystery
+ Romance
+ Science Fiction
+ Short
+ Suspense
+ Thriller
+ Western
+
+ Genre Filter
+ Löschen von Dateien bestätigen?
+ Diesen Eintrag löschen? Diese Aktion löscht die Mediendatei und damit verbundene Daten.
+
+ Als 'gesehen' markieren
+ Als 'ungesehen' markieren
+ Zu Favoriten hinzufügen
+ Von Favoriten entfernen
+ Sortiere nach ...
+ Sortierreihenfolge absteigend
+ Sortierreihenfolge aufsteigend
+ Zeige Mitwirkende
+
+
+ Fortsetzen
+ Fortsetzen bei
+ Am Anfang starten
+
+ Benutzeroberfläche
+ Lade Streaminformationen
+ Lade Darsteller
+ Lade Inhaltsübersicht
+ Rücksprung bei Fortsetzen
+ Bei Stop nach x % als 'gesehen' markieren
+ Medien- und 'Abgespielt'-Zähler hinzufügen
+ - Aktualisierungsintervall von Hintergrundbildern (Sekunden)
+ Prozentanzeige für Fortsetzen
+ Episodennummer hinzufügen
+ Ladefortschritt anzeigen
+ Lade Inhalt
+ Lade Daten
+ Verarbeite Json Daten
+ Lade Json Daten
+ Fertig
+ Verarbeite Eintrag :
+ Löschen von gesehenen Episoden anbieten
+ Abspielfehler
+ Dieser Eintrag ist nicht abspielbar
+ Lokaler Pfad erkannt
+ Warnung
+ Debug Logging aktiviert.
+ Dies beeinträchtigt die Performance.
+ Fehler
+ XBMB3C-Service läuft nicht
+ Bitte XBMC neustarten
+ Suche
+
+ Themen-Musik aktivieren (Erfordert Neustart)
+ - Themen-Musik in Schleife abspielen
+ Laden im Hintergrund aktivieren (Erfordert Neustart)
+ Dienste
+ Info-Loader aktivieren (Erfordert Neustart)
+ Menü-Loader aktivieren (Erfordert Neustart)
+ WebSocket Fernbedienung aktivieren (Erfordert Neustart)
+ 'Laufende Medien'-Loader aktivieren (Erfordert Neustart)
+ 'Zuletzt hinzugefügt' Loader aktivieren (Erfordert Neustart)
+ 'Zufallsmedien'-Loader aktivieren (Erfordert Neustart)
+ 'Nächste'-Loader aktivieren (Erfordert Neustart)
+
+ Skin unterstützt das Setzen von Views nicht
+ Aktion bei Auswahl (Erfordert Neustart)
+
+ Indikatoren
+ - 'Gesehen'-Indikator anzeigen
+ - Zähler für ungesehene Medien anzeigen
+ - Abspiel-Prozentanzeige aktivieren
+ Sortiere 'Nächste' nach Serientitel
+ Deaktiviere erweiterte Bilder (z.B. CoverArt)
+ Metadaten
+ Grafiken
+ Videoqualität
+
+ 'Empfohlen'-Loader aktivieren (Erfordert Neustart)
+ Staffelnummer hinzufügen
+ Serienstaffeln reduzieren
+ Direkte Wiedergabe - HTTP
+ Direkte Wiedergabe
+ Transkodierung
+ Serversuche erfolgreich
+ Server gefunden
+ Addresse :
+
+ Alle Filme
+ Alle Serien
+ Alles an Musik
+ Kanäle
+ Zuletzt hinzugefügte Filme
+ Zuletzt hinzugefügte Episoden
+ Zuletzt hinzugefügte Alben
+ Begonnene Filme
+ Begonnene Episoden
+ Nächste Episoden
+ Favorisierte Filme
+ Favorisierte Serien
+ Favorisierte Episoden
+ Häufig gespielte Alben
+ Anstehende Serien
+ Sammlungen
+ Trailer
+ Musikvideos
+ Fotos
+ Ungesehene Filme
+ Filmgenres
+ Studios
+ Filmdarsteller
+ Ungesehene Episoden
+ Seriengenres
+ Fernsehsender
+ Seriendarsteller
+ Wiedergabelisten
+ Suche
+ Ansichten festlegen
+
+ Wähle Benutzer
+ Messung aktiviert.
+ Bitte daran denken, nach dem Testen wieder zu deaktivieren.
+ Fehler in ArtworkRotationThread
+ Verbindung zum Server fehlgeschlagen
+ Fehler in LoadMenuOptionsThread
+
+ 'Playlist'-Loader aktivieren (Erfordert Neustart)
+
+ Songs
+ Alben
+ Album-Interpreten
+ Interpreten
+ Musik-Genres
+
+ Themen-Videos aktivieren (Erfordert Neustart)
+ - Themen-Videos in Schleife abspielen
+
+ Festgelegte Ansichten deaktivieren
+ Schnelleres Laden der Daten aktivieren
+ Spiele weitere Episoden einer Staffel automatisch ab
+ Aktiviere gruppierte Darstellung von Sammlungen (Erfordert Neustart)
+ Bilder komprimieren
+
+
+ Aktiviert
+ Zurücksetzen
+ Filme
+ BoxSets
+ Trailer
+ Serien
+ Staffeln
+ Episoden
+ Interpreten
+ Alben
+ Musikvideos
+ Musikstücke
+
+
diff --git a/resources/lib/API.py b/resources/lib/API.py
new file mode 100644
index 00000000..db796242
--- /dev/null
+++ b/resources/lib/API.py
@@ -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
\ No newline at end of file
diff --git a/resources/lib/ClientInformation.py b/resources/lib/ClientInformation.py
new file mode 100644
index 00000000..915e67e9
--- /dev/null
+++ b/resources/lib/ClientInformation.py
@@ -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"
diff --git a/resources/lib/ConnectionManager.py b/resources/lib/ConnectionManager.py
new file mode 100644
index 00000000..6a9b5995
--- /dev/null
+++ b/resources/lib/ConnectionManager.py
@@ -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 != ""):
+ 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 = ("", 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
+
diff --git a/resources/lib/DownloadUtils.py b/resources/lib/DownloadUtils.py
new file mode 100644
index 00000000..b1974e1e
--- /dev/null
+++ b/resources/lib/DownloadUtils.py
@@ -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)
diff --git a/resources/lib/KodiMonitor.py b/resources/lib/KodiMonitor.py
new file mode 100644
index 00000000..df386390
--- /dev/null
+++ b/resources/lib/KodiMonitor.py
@@ -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)
+
diff --git a/resources/lib/LibrarySync.py b/resources/lib/LibrarySync.py
new file mode 100644
index 00000000..5bc2587a
--- /dev/null
+++ b/resources/lib/LibrarySync.py
@@ -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
diff --git a/resources/lib/PlayUtils.py b/resources/lib/PlayUtils.py
new file mode 100644
index 00000000..d321756e
--- /dev/null
+++ b/resources/lib/PlayUtils.py
@@ -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
+
diff --git a/resources/lib/PlaybackUtils.py b/resources/lib/PlaybackUtils.py
new file mode 100644
index 00000000..949fdc7b
--- /dev/null
+++ b/resources/lib/PlaybackUtils.py
@@ -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})
diff --git a/resources/lib/Player.py b/resources/lib/Player.py
new file mode 100644
index 00000000..d3ab534a
--- /dev/null
+++ b/resources/lib/Player.py
@@ -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()
\ No newline at end of file
diff --git a/resources/lib/Utils.py b/resources/lib/Utils.py
new file mode 100644
index 00000000..dd6dca90
--- /dev/null
+++ b/resources/lib/Utils.py
@@ -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
+
+
\ No newline at end of file
diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py
new file mode 100644
index 00000000..b93054b3
--- /dev/null
+++ b/resources/lib/__init__.py
@@ -0,0 +1 @@
+# Dummy file to make this directory a package.
diff --git a/resources/mb3.png b/resources/mb3.png
new file mode 100644
index 00000000..fc266e2e
Binary files /dev/null and b/resources/mb3.png differ
diff --git a/resources/settings.xml b/resources/settings.xml
new file mode 100644
index 00000000..9423ff3b
--- /dev/null
+++ b/resources/settings.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/service.py b/service.py
new file mode 100644
index 00000000..266db3b3
--- /dev/null
+++ b/service.py
@@ -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()