|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||||
|
</project>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 2.7.7 (D:/Python27/python.exe)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/plugin.video.torrenter.iml" filepath="$PROJECT_DIR$/.idea/plugin.video.torrenter.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<component name="DependencyValidationManager">
|
||||||
|
<state>
|
||||||
|
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||||
|
</state>
|
||||||
|
</component>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
|
@ -0,0 +1,380 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="56e91349-2444-4553-bc78-90c2bab49e9c" name="Default" comment="">
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/AddonWindow/ContentPanel.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/AddonWindow/DialogCloseButton-focus.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/AddonWindow/DialogCloseButton.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/EZTV.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/ExtraTorrent.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/Button/KeyboardKey.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/Button/KeyboardKeyNF.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/KickAssSo.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/KinoZalTV.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/List/MenuItemFO.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/RadioButton/MenuItemFO.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/List/MenuItemNF.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/RadioButton/MenuItemNF.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/Nyaa.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/OldPirateBay3.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/OpenSharing.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/RiperAM.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/AddonWindow/SKINDEFAULT.jpg" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/TFileME.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/Edit/black-back2.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/images/black.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/Edit/button-focus.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/clear.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/AddonWindow/dialogheader.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/fav.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/history2.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icon.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/list.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/magnet.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/media.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/nnm-club.ru.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/Slider/osd_slider_bg.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/Slider/osd_slider_bg_2.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/Slider/osd_slider_nib.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/Slider/osd_slider_nibNF.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/RadioButton/radiobutton-focus.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/textures/default/RadioButton/radiobutton-nofocus.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/rutor.org.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/rutracker.org.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/search.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/settings.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/icons/thepiratebay.se.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/torrent-client.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/torrentPlayer.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/unfav.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/icons/video.png" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/AceStream.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/BeautifulSoup.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/contenters/CXZ.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/Content.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/Core.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/Downloader.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/contenters/EZTV.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/EZTV.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/ExtraTorrent.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/contenters/FastTorrent.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/kinopoisk/HTTP.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/contenters/IMDB.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/KickAssSo.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/contenters/KinoPoisk.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/unused/KinoZalTV.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/kinopoisk/LOGGER.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/Libtorrent.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/Localization.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/NNMClubRu.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/Nyaa.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/OldPirateBay.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/OpenSharing.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/Player.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/Proxier.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/README.txt" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/Rates.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/contenters/RiperAM.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/RiperAM.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/RuTorOrg.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/RuTrackerOrg.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/SearcherABC.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/fuzzywuzzy/StringMatcher.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/TFileME.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/searchers/ThePirateBaySe.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/fuzzywuzzy/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/kinopoisk/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/contrib/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/packages/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/util/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/__init__.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/_collections.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/adapters.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/addon.xml" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/pyxbmct/addonwindow.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/api.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/auth.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/aztypes.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/big5freq.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/big5prober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/cacert.pem" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/cache.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/cal.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/certs.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/changelog.txt" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/chardetect.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/chardistribution.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/charsetgroupprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/charsetprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/class_defs.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/classes.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/codingstatemachine.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/kinopoisk/common.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/compat.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/compat.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/connection.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/util/connection.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/connectionpool.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/constants.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/controlcenter.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/convert.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/cookies.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/core.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/cp949prober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/debug.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/default.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/errors.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/escprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/escsm.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/eucjpprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/euckrfreq.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/euckrprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/euctwfreq.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/euctwprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/exceptions.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/exceptions.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/fields.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/filepost.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/functions.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/fuzzywuzzy/fuzz.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/gb2312freq.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/gb2312prober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/hebrewprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/hooks.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/html.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/interact.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/jisfreq.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/jpcntx.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/kinopoisks.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/langbulgarianmodel.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/langcyrillicmodel.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/langgreekmodel.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/langhebrewmodel.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/langhungarianmodel.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/langthaimodel.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/latin1prober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/logutils.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/main.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/mbcharsetprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/mbcsgroupprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/mbcssm.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/models.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/net.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/net.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/contrib/ntlmpool.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/obj_impl.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/objects.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/packages/ordered_dict.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/kinopoisk/pageparser.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/persistency.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/kinopoisk/pluginsettings.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/poolmanager.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/fuzzywuzzy/process.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/contrib/pyopenssl.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/request.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/util/request.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/response.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/util/response.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/util/retry.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/sbcharsetprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/sbcsgroupprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/scrapers.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/scripting.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/sessions.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/settings.xml" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/packages/six.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/sjisprober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/util/ssl_.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/status_codes.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/fuzzywuzzy/string_processing.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/language/English/strings.xml" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/language/Russian/strings.xml" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/structures.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/util/timeout.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/tmdb.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/tmdbs.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/kinopoisk/translit.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/tvdb.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/universaldetector.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/urllib3/util/url.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/packages/chardet/utf8prober.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/fuzzywuzzy/utils.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/scrapers/requests/utils.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/utils.py" />
|
||||||
|
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/resources/utorrent/dopal/xmlutils.py" />
|
||||||
|
</list>
|
||||||
|
<ignored path="plugin.video.torrenter.iws" />
|
||||||
|
<ignored path=".idea/workspace.xml" />
|
||||||
|
<ignored path=".idea/modules.xml" />
|
||||||
|
<ignored path=".idea/vcs.xml" />
|
||||||
|
<ignored path=".idea/encodings.xml" />
|
||||||
|
<ignored path=".idea/misc.xml" />
|
||||||
|
<ignored path=".idea/plugin.video.torrenter.iml" />
|
||||||
|
<ignored path=".idea/scopes/scope_settings.xml" />
|
||||||
|
<option name="TRACKING_ENABLED" value="true" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
|
||||||
|
<component name="CreatePatchCommitExecutor">
|
||||||
|
<option name="PATCH_PATH" value="" />
|
||||||
|
</component>
|
||||||
|
<component name="DaemonCodeAnalyzer">
|
||||||
|
<disable_hints />
|
||||||
|
</component>
|
||||||
|
<component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
|
||||||
|
<component name="FavoritesManager">
|
||||||
|
<favorites_list name="plugin.video.torrenter" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectFrameBounds">
|
||||||
|
<option name="x" value="-8" />
|
||||||
|
<option name="y" value="-8" />
|
||||||
|
<option name="width" value="1936" />
|
||||||
|
<option name="height" value="1056" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
|
||||||
|
<OptionsSetting value="true" id="Add" />
|
||||||
|
<OptionsSetting value="true" id="Remove" />
|
||||||
|
<OptionsSetting value="true" id="Checkout" />
|
||||||
|
<OptionsSetting value="true" id="Update" />
|
||||||
|
<OptionsSetting value="true" id="Status" />
|
||||||
|
<OptionsSetting value="true" id="Edit" />
|
||||||
|
<ConfirmationsSetting value="0" id="Add" />
|
||||||
|
<ConfirmationsSetting value="0" id="Remove" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectReloadState">
|
||||||
|
<option name="STATE" value="0" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectView">
|
||||||
|
<navigator currentView="ProjectPane" proportions="" version="1">
|
||||||
|
<flattenPackages />
|
||||||
|
<showMembers />
|
||||||
|
<showModules />
|
||||||
|
<showLibraryContents />
|
||||||
|
<hideEmptyPackages />
|
||||||
|
<abbreviatePackageNames />
|
||||||
|
<autoscrollToSource />
|
||||||
|
<autoscrollFromSource />
|
||||||
|
<sortByType />
|
||||||
|
</navigator>
|
||||||
|
<panes>
|
||||||
|
<pane id="ProjectPane">
|
||||||
|
<subPane>
|
||||||
|
<PATH>
|
||||||
|
<PATH_ELEMENT>
|
||||||
|
<option name="myItemId" value="plugin.video.torrenter" />
|
||||||
|
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||||
|
</PATH_ELEMENT>
|
||||||
|
</PATH>
|
||||||
|
<PATH>
|
||||||
|
<PATH_ELEMENT>
|
||||||
|
<option name="myItemId" value="plugin.video.torrenter" />
|
||||||
|
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||||
|
</PATH_ELEMENT>
|
||||||
|
<PATH_ELEMENT>
|
||||||
|
<option name="myItemId" value="plugin.video.torrenter" />
|
||||||
|
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||||
|
</PATH_ELEMENT>
|
||||||
|
</PATH>
|
||||||
|
</subPane>
|
||||||
|
</pane>
|
||||||
|
<pane id="Scope" />
|
||||||
|
</panes>
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<list size="0" />
|
||||||
|
</component>
|
||||||
|
<component name="ShelveChangesManager" show_recycled="false" />
|
||||||
|
<component name="SvnConfiguration">
|
||||||
|
<configuration />
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="56e91349-2444-4553-bc78-90c2bab49e9c" name="Default" comment="" />
|
||||||
|
<created>1420801533300</created>
|
||||||
|
<updated>1420801533300</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TodoView" selected-index="0">
|
||||||
|
<todo-panel id="selected-file">
|
||||||
|
<are-packages-shown value="false" />
|
||||||
|
<are-modules-shown value="false" />
|
||||||
|
<flatten-packages value="false" />
|
||||||
|
<is-autoscroll-to-source value="false" />
|
||||||
|
</todo-panel>
|
||||||
|
<todo-panel id="all">
|
||||||
|
<are-packages-shown value="false" />
|
||||||
|
<are-modules-shown value="false" />
|
||||||
|
<flatten-packages value="false" />
|
||||||
|
<is-autoscroll-to-source value="false" />
|
||||||
|
</todo-panel>
|
||||||
|
<todo-panel id="default-changelist">
|
||||||
|
<are-packages-shown value="false" />
|
||||||
|
<are-modules-shown value="false" />
|
||||||
|
<flatten-packages value="false" />
|
||||||
|
<is-autoscroll-to-source value="false" />
|
||||||
|
</todo-panel>
|
||||||
|
</component>
|
||||||
|
<component name="ToolWindowManager">
|
||||||
|
<frame x="-8" y="-8" width="1936" height="1056" extended-state="6" />
|
||||||
|
<editor active="false" />
|
||||||
|
<layout>
|
||||||
|
<window_info id="Changes" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32925472" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Terminal" active="true" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.32925472" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32925472" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.24973656" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
|
||||||
|
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
|
||||||
|
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
|
||||||
|
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32925472" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="SLIDING" type="SLIDING" visible="false" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
|
||||||
|
</layout>
|
||||||
|
</component>
|
||||||
|
<component name="Vcs.Log.UiProperties">
|
||||||
|
<option name="RECENTLY_FILTERED_USER_GROUPS">
|
||||||
|
<collection />
|
||||||
|
</option>
|
||||||
|
<option name="RECENTLY_FILTERED_BRANCH_GROUPS">
|
||||||
|
<collection />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="VcsContentAnnotationSettings">
|
||||||
|
<option name="myLimit" value="2678400000" />
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<option name="myTodoPanelSettings">
|
||||||
|
<TodoPanelSettings />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="XDebuggerManager">
|
||||||
|
<breakpoint-manager />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import urllib2
|
||||||
|
import urllib
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
from StringIO import StringIO
|
||||||
|
import gzip
|
||||||
|
from functions import file_decode, file_encode
|
||||||
|
|
||||||
|
from functions import magnet_alert
|
||||||
|
import xbmcvfs
|
||||||
|
|
||||||
|
|
||||||
|
class AceStream:
|
||||||
|
try:
|
||||||
|
fpath = os.path.expanduser("~")
|
||||||
|
pfile = os.path.join(fpath, 'AppData\Roaming\ACEStream\engine', 'acestream.port')
|
||||||
|
gf = open(pfile, 'r')
|
||||||
|
aceport = int(gf.read())
|
||||||
|
gf.close()
|
||||||
|
print aceport
|
||||||
|
except:
|
||||||
|
aceport = 62062
|
||||||
|
|
||||||
|
torrentFile = None
|
||||||
|
magnetLink = None
|
||||||
|
storageDirectory = ''
|
||||||
|
torrentFilesDirectory = 'torrents'
|
||||||
|
startPart = 0
|
||||||
|
endPart = 0
|
||||||
|
partOffset = 0
|
||||||
|
torrentHandle = None
|
||||||
|
session = None
|
||||||
|
downloadThread = None
|
||||||
|
threadComplete = False
|
||||||
|
lt = None
|
||||||
|
|
||||||
|
def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'):
|
||||||
|
try:
|
||||||
|
from ASCore import TSengine as tsengine
|
||||||
|
|
||||||
|
print 'Imported TSengine from ASCore'
|
||||||
|
except Exception, e:
|
||||||
|
print 'Error importing TSengine from ASCore. Exception: ' + str(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
self.TSplayer = tsengine()
|
||||||
|
del tsengine
|
||||||
|
self.torrentFilesDirectory = torrentFilesDirectory
|
||||||
|
self.storageDirectory = storageDirectory
|
||||||
|
_path=os.path.join(self.storageDirectory, self.torrentFilesDirectory)+os.sep
|
||||||
|
if not xbmcvfs.exists(_path):
|
||||||
|
xbmcvfs.mkdirs(_path)
|
||||||
|
if xbmcvfs.exists(torrentFile):
|
||||||
|
self.torrentFile = torrentFile
|
||||||
|
content = xbmcvfs.File(torrentFile, "rb").read()
|
||||||
|
self.torrentFileInfo = self.TSplayer.load_torrent(base64.b64encode(content), 'RAW')
|
||||||
|
elif re.match("^magnet\:.+$", torrentFile):
|
||||||
|
magnet_alert()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def __exit__(self):
|
||||||
|
self.TSplayer.end()
|
||||||
|
|
||||||
|
def play_url_ind(self, ind, label, icon):
|
||||||
|
self.TSplayer.play_url_ind(int(ind), label, str(icon), '')
|
||||||
|
|
||||||
|
def saveTorrent(self, torrentUrl):
|
||||||
|
if re.match("^magnet\:.+$", torrentUrl):
|
||||||
|
magnet_alert()
|
||||||
|
exit()
|
||||||
|
else:
|
||||||
|
torrentFile = self.storageDirectory + os.sep + self.torrentFilesDirectory + os.sep + self.md5(
|
||||||
|
torrentUrl) + '.torrent'
|
||||||
|
try:
|
||||||
|
if xbmcvfs.exists(file_decode(torrentUrl)):
|
||||||
|
content = xbmcvfs.File(file_decode(torrentUrl), "rb").read()
|
||||||
|
else:
|
||||||
|
request = urllib2.Request(torrentUrl)
|
||||||
|
request.add_header('Referer', torrentUrl)
|
||||||
|
request.add_header('Accept-encoding', 'gzip')
|
||||||
|
result = urllib2.urlopen(request)
|
||||||
|
if result.info().get('Content-Encoding') == 'gzip':
|
||||||
|
buf = StringIO(result.read())
|
||||||
|
f = gzip.GzipFile(fileobj=buf)
|
||||||
|
content = f.read()
|
||||||
|
else:
|
||||||
|
content = result.read()
|
||||||
|
|
||||||
|
localFile = xbmcvfs.File(torrentFile, "w+b")
|
||||||
|
localFile.write(content)
|
||||||
|
localFile.close()
|
||||||
|
except Exception, e:
|
||||||
|
print 'Unable to save torrent file from "' + torrentUrl + '" to "' + torrentFile + '" in AceStream::saveTorrent' + '. Exception: ' + str(e)
|
||||||
|
return
|
||||||
|
if xbmcvfs.exists(torrentFile):
|
||||||
|
self.torrentFile = torrentFile
|
||||||
|
self.torrentFileInfo = self.TSplayer.load_torrent(base64.b64encode(content), 'RAW')
|
||||||
|
return self.torrentFile
|
||||||
|
|
||||||
|
def getMagnetInfo(self):
|
||||||
|
magnet_alert()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def magnetToTorrent(self, magnet):
|
||||||
|
magnet_alert()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def getFilePath(self, contentId=0):
|
||||||
|
fileList = self.getContentList()
|
||||||
|
for i in fileList:
|
||||||
|
if i['ind'] == contentId:
|
||||||
|
return os.path.join(file_encode(self.storageDirectory),i['title'])
|
||||||
|
|
||||||
|
def getContentList(self):
|
||||||
|
filelist = []
|
||||||
|
for k, v in self.TSplayer.files.iteritems():
|
||||||
|
stringdata = {"title": urllib.unquote_plus(k), "ind": int(v)}
|
||||||
|
filelist.append(stringdata)
|
||||||
|
return filelist
|
||||||
|
|
||||||
|
def md5(self, string):
|
||||||
|
hasher = hashlib.md5()
|
||||||
|
try:
|
||||||
|
hasher.update(string)
|
||||||
|
except:
|
||||||
|
hasher.update(string.encode('utf-8', 'ignore'))
|
||||||
|
return hasher.hexdigest()
|
|
@ -0,0 +1,233 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for Kodi
|
||||||
|
Copyright (C) 2012 DiMartino
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
import cookielib
|
||||||
|
import re
|
||||||
|
from StringIO import StringIO
|
||||||
|
import gzip
|
||||||
|
import HTMLParser
|
||||||
|
|
||||||
|
import Localization
|
||||||
|
|
||||||
|
|
||||||
|
class Content:
|
||||||
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
|
searchIcon = '/icons/video.png'
|
||||||
|
sourceWeight = 1
|
||||||
|
cookieJar = None
|
||||||
|
|
||||||
|
def isLabel(self):
|
||||||
|
return 'Should search on ruhunt?'
|
||||||
|
|
||||||
|
def isPages(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isScrappable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isInfoLink(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isSearchOption(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
category_dict = {
|
||||||
|
'sites': ('[B]by Site[/B]',),
|
||||||
|
'search': ('[B]Search[/B]',),
|
||||||
|
'movies': ('Forieng Movies',),
|
||||||
|
'rus_movies': ('Russian Movies',),
|
||||||
|
'tvshows': ('TV Shows',),
|
||||||
|
'cartoons': ('Cartoons',),
|
||||||
|
'hot': ('Hot & New',),
|
||||||
|
'top': ('Top All Time',),
|
||||||
|
'anime': ('Anime',),
|
||||||
|
'year': {'year': 'by Year', },
|
||||||
|
'genre': {'genre': 'by Genre',
|
||||||
|
'action': ('Action',),
|
||||||
|
'comedy': ('Comedy',),
|
||||||
|
'documentary': ('Documentary',),
|
||||||
|
'drama': ('Drama',),
|
||||||
|
'fantasy': ('Fantasy',),
|
||||||
|
'horror': ('Horror',),
|
||||||
|
'romance': ('Romance',),
|
||||||
|
'thriller': ('Thriller',),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for y in range(2015, 1970, -1):
|
||||||
|
category_dict['year'][str(y)] = (str(y), '/top/y/%s/' % str(y))
|
||||||
|
|
||||||
|
def get_contentList(self, category, subcategory=None, page=None):
|
||||||
|
'''
|
||||||
|
Retrieve keyword from the input and return a list of tuples:
|
||||||
|
filesList.append((
|
||||||
|
int(weight),
|
||||||
|
int(seeds),
|
||||||
|
str(title),
|
||||||
|
str(link),
|
||||||
|
str(image),
|
||||||
|
))
|
||||||
|
'''
|
||||||
|
return
|
||||||
|
|
||||||
|
def has_category(self, category, subcategory=None):
|
||||||
|
has_category = False
|
||||||
|
if not subcategory or subcategory == True:
|
||||||
|
if category in self.category_dict.keys():
|
||||||
|
has_category = True
|
||||||
|
else:
|
||||||
|
if category in self.category_dict:
|
||||||
|
cat_con = self.category_dict[category]
|
||||||
|
if isinstance(cat_con, dict):
|
||||||
|
if subcategory in cat_con.keys():
|
||||||
|
has_category = True
|
||||||
|
return has_category
|
||||||
|
|
||||||
|
def get_url(self, category, subcategory, page, baseurl):
|
||||||
|
if not subcategory or subcategory == True or category == 'search':
|
||||||
|
get = self.category_dict[category]
|
||||||
|
else:
|
||||||
|
get = self.category_dict[category][subcategory]
|
||||||
|
|
||||||
|
if category == 'search': get = (get[0], get[1] % urllib.quote_plus(subcategory.encode('utf-8')))
|
||||||
|
|
||||||
|
if not page or page == 1:
|
||||||
|
url = baseurl + get[1]
|
||||||
|
else:
|
||||||
|
property = self.get_property(category, subcategory)
|
||||||
|
|
||||||
|
page_url = property['page'] % (property['second_page'] + ((page - 2) * property['increase']))
|
||||||
|
url = baseurl + str(page_url)
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def get_property(self, category, subcategory=None):
|
||||||
|
has_property = False
|
||||||
|
property = {}
|
||||||
|
if not subcategory or subcategory == True:
|
||||||
|
if category in self.category_dict.keys():
|
||||||
|
try:
|
||||||
|
property = self.category_dict[category][2]
|
||||||
|
if isinstance(property, dict):
|
||||||
|
has_property = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if category in self.category_dict:
|
||||||
|
cat_con = self.category_dict[category]
|
||||||
|
if isinstance(cat_con, dict):
|
||||||
|
if subcategory in cat_con.keys():
|
||||||
|
try:
|
||||||
|
property = cat_con[subcategory][2]
|
||||||
|
if isinstance(property, dict):
|
||||||
|
has_property = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if has_property:
|
||||||
|
if category == 'search': property['page'] = property['page'] % urllib.quote_plus(
|
||||||
|
subcategory.encode('utf-8'))
|
||||||
|
return property
|
||||||
|
|
||||||
|
|
||||||
|
def makeRequest(self, url, data={}, headers=[]):
|
||||||
|
self.cookieJar = cookielib.CookieJar()
|
||||||
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookieJar))
|
||||||
|
opener.addheaders = headers
|
||||||
|
if 0 < len(data):
|
||||||
|
encodedData = urllib.urlencode(data)
|
||||||
|
else:
|
||||||
|
encodedData = None
|
||||||
|
response = opener.open(url, encodedData)
|
||||||
|
if response.info().get('Content-Encoding') == 'gzip':
|
||||||
|
buf = StringIO(response.read())
|
||||||
|
f = gzip.GzipFile(fileobj=buf)
|
||||||
|
response = f.read()
|
||||||
|
else:
|
||||||
|
response = response.read()
|
||||||
|
return response
|
||||||
|
|
||||||
|
htmlCodes = (
|
||||||
|
('&', '&'),
|
||||||
|
('<', '<'),
|
||||||
|
('>', '>'),
|
||||||
|
('"', '"'),
|
||||||
|
("'", '''),
|
||||||
|
(' ', ' ',),
|
||||||
|
('"', '«', ),
|
||||||
|
('"', '»', ),
|
||||||
|
('·', '·',),
|
||||||
|
('e', 'é',),
|
||||||
|
('e', 'è',),
|
||||||
|
('&', '&',),
|
||||||
|
('u', 'ù',),
|
||||||
|
('u', 'ú',),
|
||||||
|
('o', 'ô',),
|
||||||
|
('u', 'û'),
|
||||||
|
('-', '–'),
|
||||||
|
)
|
||||||
|
stripPairs = (
|
||||||
|
('<p>', '\n'),
|
||||||
|
('<li>', '\n'),
|
||||||
|
('<br>', '\n'),
|
||||||
|
('<.+?>', ' '),
|
||||||
|
('</.+?>', ' '),
|
||||||
|
( ' ', ' ',),
|
||||||
|
('«', '"',),
|
||||||
|
('»', '"', ),
|
||||||
|
('–', '-'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def unescape(self, string):
|
||||||
|
pars = HTMLParser.HTMLParser()
|
||||||
|
return pars.unescape(string)
|
||||||
|
#for (symbol, code) in self.htmlCodes:
|
||||||
|
# try:
|
||||||
|
# string = re.sub(code, symbol, string)
|
||||||
|
# except:
|
||||||
|
# pass
|
||||||
|
#return string
|
||||||
|
|
||||||
|
def stripHtml(self, string):
|
||||||
|
for (html, replacement) in self.stripPairs:
|
||||||
|
string = re.sub(html, replacement, string)
|
||||||
|
return string
|
||||||
|
|
||||||
|
def translate(self, category, subcategory=None):
|
||||||
|
if not subcategory:
|
||||||
|
if isinstance(self.category_dict.get(category), dict):
|
||||||
|
return self.localize(self.category_dict.get(category).get(category))
|
||||||
|
else:
|
||||||
|
return self.localize(self.category_dict.get(category)[0])
|
||||||
|
else:
|
||||||
|
return self.localize(self.category_dict.get(category).get(subcategory)[0])
|
||||||
|
|
||||||
|
def localize(self, string):
|
||||||
|
if string:
|
||||||
|
try:
|
||||||
|
return Localization.localize(string)
|
||||||
|
except:
|
||||||
|
return string
|
||||||
|
else:
|
||||||
|
return 'Empty string'
|
||||||
|
|
||||||
|
#print str(Content().has_category('x'))
|
|
@ -0,0 +1,132 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import hashlib, sys
|
||||||
|
|
||||||
|
import Libtorrent
|
||||||
|
import AceStream
|
||||||
|
|
||||||
|
|
||||||
|
class Torrent():
|
||||||
|
__settings__ = sys.modules["__main__"].__settings__
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'):
|
||||||
|
self.get_torrent_client()
|
||||||
|
if self.player == 'libtorrent':
|
||||||
|
self.player = Libtorrent.Libtorrent(storageDirectory, torrentFile, torrentFilesDirectory)
|
||||||
|
|
||||||
|
elif self.player == 'acestream':
|
||||||
|
self.player = AceStream.AceStream(storageDirectory, torrentFile, torrentFilesDirectory)
|
||||||
|
|
||||||
|
def __exit__(self):
|
||||||
|
self.player.__exit__()
|
||||||
|
|
||||||
|
def get_torrent_client(self):
|
||||||
|
player = self.__settings__.getSetting("torrent_player")
|
||||||
|
if player == '0':
|
||||||
|
self.player = 'libtorrent'
|
||||||
|
elif player == '1':
|
||||||
|
self.player = 'acestream'
|
||||||
|
|
||||||
|
def play_url_ind(self, ind, label, icon):
|
||||||
|
return self.player.play_url_ind(int(ind), label, str(icon))
|
||||||
|
|
||||||
|
def saveTorrent(self, torrentUrl):
|
||||||
|
return self.player.saveTorrent(torrentUrl)
|
||||||
|
|
||||||
|
def getMagnetInfo(self):
|
||||||
|
return self.player.getMagnetInfo()
|
||||||
|
|
||||||
|
def magnetToTorrent(self, magnet):
|
||||||
|
return self.player.magnetToTorrent(magnet)
|
||||||
|
|
||||||
|
def getUploadRate(self):
|
||||||
|
return self.player.getUploadRate()
|
||||||
|
|
||||||
|
def getDownloadRate(self):
|
||||||
|
return self.player.getDownloadRate()
|
||||||
|
|
||||||
|
def getPeers(self):
|
||||||
|
return self.player.getPeers()
|
||||||
|
|
||||||
|
def getSeeds(self):
|
||||||
|
return self.player.getMagnetInfo()
|
||||||
|
|
||||||
|
def getFileSize(self, contentId=0):
|
||||||
|
return self.player.getFileSize(contentId)
|
||||||
|
|
||||||
|
def getFilePath(self, contentId=0):
|
||||||
|
return self.player.getFilePath(contentId)
|
||||||
|
|
||||||
|
def getContentList(self):
|
||||||
|
#print str(self.player.getContentList())
|
||||||
|
return self.player.getContentList()
|
||||||
|
|
||||||
|
def setUploadLimit(self, bytesPerSecond):
|
||||||
|
return self.player.setUploadLimit(bytesPerSecond)
|
||||||
|
|
||||||
|
def setDownloadLimit(self, bytesPerSecond):
|
||||||
|
return self.player.setDownloadLimit(bytesPerSecond)
|
||||||
|
|
||||||
|
def stopSession(self):
|
||||||
|
return self.player.stopSession()
|
||||||
|
|
||||||
|
def md5(self, string):
|
||||||
|
hasher = hashlib.md5()
|
||||||
|
try:
|
||||||
|
hasher.update(string)
|
||||||
|
except:
|
||||||
|
hasher.update(string.encode('utf-8', 'ignore'))
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
def downloadProcess(self, contentId):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initSession(self):
|
||||||
|
return self.player.initSession()
|
||||||
|
|
||||||
|
def startSession(self):
|
||||||
|
return self.player.startSession()
|
||||||
|
|
||||||
|
def continueSession(self, contentId=0, Offset=155, seeding=True):
|
||||||
|
return self.player.continueSession(contentId, Offset, seeding)
|
||||||
|
|
||||||
|
def addToSeeding(self):
|
||||||
|
return self.player.addToSeeding()
|
||||||
|
|
||||||
|
def fetchParts(self):
|
||||||
|
return self.player.fetchParts()
|
||||||
|
|
||||||
|
def checkThread(self):
|
||||||
|
return self.player.checkThread()
|
||||||
|
|
||||||
|
def _makedirs(self, _path):
|
||||||
|
return self.player._makedirs(_path)
|
||||||
|
|
||||||
|
def debug(self):
|
||||||
|
return self.player.debug()
|
||||||
|
|
||||||
|
def dump(self, obj):
|
||||||
|
for attr in dir(obj):
|
||||||
|
try:
|
||||||
|
print "'%s':'%s'," % (attr, getattr(obj, attr))
|
||||||
|
except:
|
||||||
|
pass
|
|
@ -0,0 +1,399 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
#import time
|
||||||
|
import thread
|
||||||
|
import os
|
||||||
|
import urllib2
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import platform
|
||||||
|
from StringIO import StringIO
|
||||||
|
import gzip
|
||||||
|
from functions import file_decode, file_encode, isSubtitle
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
import xbmcvfs
|
||||||
|
import Localization
|
||||||
|
|
||||||
|
|
||||||
|
class Libtorrent:
|
||||||
|
torrentFile = None
|
||||||
|
magnetLink = None
|
||||||
|
storageDirectory = ''
|
||||||
|
torrentFilesDirectory = 'torrents'
|
||||||
|
startPart = 0
|
||||||
|
endPart = 0
|
||||||
|
partOffset = 0
|
||||||
|
torrentHandle = None
|
||||||
|
session = None
|
||||||
|
downloadThread = None
|
||||||
|
threadComplete = False
|
||||||
|
threadSeeding = False
|
||||||
|
seedingHandle = None
|
||||||
|
lt = None
|
||||||
|
|
||||||
|
def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'):
|
||||||
|
|
||||||
|
try:
|
||||||
|
import libtorrent
|
||||||
|
|
||||||
|
print 'Imported libtorrent v' + libtorrent.version + ' from system'
|
||||||
|
except Exception, e:
|
||||||
|
print 'Error importing from system. Exception: ' + str(e)
|
||||||
|
|
||||||
|
if platform.system() != 'Windows':
|
||||||
|
if sys.maxsize > 2 ** 32:
|
||||||
|
system = 'linux_x86_64'
|
||||||
|
else:
|
||||||
|
system = 'linux_x86'
|
||||||
|
else:
|
||||||
|
system = 'windows'
|
||||||
|
|
||||||
|
dirname = os.path.join(xbmc.translatePath('special://home'), 'addons', 'script.module.libtorrent',
|
||||||
|
'python_libtorrent', system)
|
||||||
|
sys.path.insert(0, dirname)
|
||||||
|
try:
|
||||||
|
import libtorrent
|
||||||
|
|
||||||
|
print 'Imported libtorrent v' + libtorrent.version + ' from python_libtorrent.' + system
|
||||||
|
except Exception, e:
|
||||||
|
print 'Error importing python_libtorrent.' + system + '. Exception: ' + str(e)
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.lt = libtorrent
|
||||||
|
del libtorrent
|
||||||
|
self.torrentFilesDirectory = torrentFilesDirectory
|
||||||
|
self.storageDirectory = storageDirectory
|
||||||
|
_path=os.path.join(self.storageDirectory, self.torrentFilesDirectory)+os.sep
|
||||||
|
if not xbmcvfs.exists(_path):
|
||||||
|
xbmcvfs.mkdirs(_path)
|
||||||
|
if xbmcvfs.exists(torrentFile):
|
||||||
|
self.torrentFile = torrentFile
|
||||||
|
self.torrentFileInfo = self.lt.torrent_info(file_decode(self.torrentFile))
|
||||||
|
elif re.match("^magnet\:.+$", torrentFile):
|
||||||
|
self.magnetLink = torrentFile
|
||||||
|
|
||||||
|
def saveTorrent(self, torrentUrl):
|
||||||
|
if re.match("^magnet\:.+$", torrentUrl):
|
||||||
|
self.magnetLink = torrentUrl
|
||||||
|
self.magnetToTorrent(torrentUrl)
|
||||||
|
return self.torrentFile
|
||||||
|
else:
|
||||||
|
torrentFile = self.storageDirectory + os.sep + self.torrentFilesDirectory + os.sep + self.md5(
|
||||||
|
torrentUrl) + '.torrent'
|
||||||
|
try:
|
||||||
|
if not re.match("^http\:.+$", torrentUrl):
|
||||||
|
content = xbmcvfs.File(file_decode(torrentUrl), "rb").read()
|
||||||
|
else:
|
||||||
|
request = urllib2.Request(torrentUrl)
|
||||||
|
request.add_header('Referer', torrentUrl)
|
||||||
|
request.add_header('Accept-encoding', 'gzip')
|
||||||
|
result = urllib2.urlopen(request)
|
||||||
|
if result.info().get('Content-Encoding') == 'gzip':
|
||||||
|
buf = StringIO(result.read())
|
||||||
|
f = gzip.GzipFile(fileobj=buf)
|
||||||
|
content = f.read()
|
||||||
|
else:
|
||||||
|
content = result.read()
|
||||||
|
|
||||||
|
localFile = xbmcvfs.File(torrentFile, "w+b")
|
||||||
|
localFile.write(content)
|
||||||
|
localFile.close()
|
||||||
|
except Exception, e:
|
||||||
|
print 'Unable to save torrent file from "' + torrentUrl + '" to "' + torrentFile + '" in Torrent::saveTorrent' + '. Exception: ' + str(e)
|
||||||
|
return
|
||||||
|
if xbmcvfs.exists(torrentFile):
|
||||||
|
try:
|
||||||
|
self.torrentFileInfo = self.lt.torrent_info(file_decode(torrentFile))
|
||||||
|
except Exception, e:
|
||||||
|
print 'Exception: ' + str(e)
|
||||||
|
xbmcvfs.delete(torrentFile)
|
||||||
|
return
|
||||||
|
baseName = file_encode(os.path.basename(self.getFilePath()))
|
||||||
|
newFile = self.storageDirectory + os.sep + self.torrentFilesDirectory + os.sep + baseName + '.' + self.md5(
|
||||||
|
torrentUrl) + '.torrent'
|
||||||
|
|
||||||
|
xbmcvfs.delete(newFile)
|
||||||
|
if not xbmcvfs.exists(newFile):
|
||||||
|
try:
|
||||||
|
xbmcvfs.rename(torrentFile, newFile)
|
||||||
|
except Exception, e:
|
||||||
|
print 'Unable to rename torrent file from "' + torrentFile + '" to "' + newFile + '" in Torrent::renameTorrent'+ '. Exception: ' + str(e)
|
||||||
|
return
|
||||||
|
self.torrentFile = newFile
|
||||||
|
if not self.torrentFileInfo:
|
||||||
|
self.torrentFileInfo = self.lt.torrent_info(file_decode(self.torrentFile))
|
||||||
|
return self.torrentFile
|
||||||
|
|
||||||
|
def getMagnetInfo(self):
|
||||||
|
magnetSettings = {
|
||||||
|
'save_path': self.storageDirectory,
|
||||||
|
'storage_mode': self.lt.storage_mode_t(0),
|
||||||
|
'paused': True,
|
||||||
|
'auto_managed': True,
|
||||||
|
'duplicate_is_error': True
|
||||||
|
}
|
||||||
|
progressBar = xbmcgui.DialogProgress()
|
||||||
|
progressBar.create(Localization.localize('Please Wait'), Localization.localize('Magnet-link is converting.'))
|
||||||
|
self.torrentHandle = self.lt.add_magnet_uri(self.session, self.magnetLink, magnetSettings)
|
||||||
|
iterator = 0
|
||||||
|
while not self.torrentHandle.has_metadata() and iterator != 100:
|
||||||
|
progressBar.update(iterator)
|
||||||
|
iterator += 1
|
||||||
|
if progressBar.iscanceled():
|
||||||
|
progressBar.update(0)
|
||||||
|
progressBar.close()
|
||||||
|
return
|
||||||
|
xbmc.sleep(500)
|
||||||
|
progressBar.update(0)
|
||||||
|
progressBar.close()
|
||||||
|
if self.torrentHandle.has_metadata():
|
||||||
|
return self.torrentHandle.get_torrent_info()
|
||||||
|
|
||||||
|
def magnetToTorrent(self, magnet):
|
||||||
|
self.magnetLink = magnet
|
||||||
|
self.initSession()
|
||||||
|
torrentInfo = self.getMagnetInfo()
|
||||||
|
try:
|
||||||
|
torrentFile = self.lt.create_torrent(torrentInfo)
|
||||||
|
baseName = file_encode(os.path.basename(self.storageDirectory + os.sep + torrentInfo.files()[0].path))
|
||||||
|
self.torrentFile = self.storageDirectory + os.sep + self.torrentFilesDirectory + os.sep + baseName + '.torrent'
|
||||||
|
torentFileHandler = xbmcvfs.File(self.torrentFile, "w+b")
|
||||||
|
torentFileHandler.write(self.lt.bencode(torrentFile.generate()))
|
||||||
|
torentFileHandler.close()
|
||||||
|
self.torrentFileInfo = self.lt.torrent_info(file_decode(self.torrentFile))
|
||||||
|
except:
|
||||||
|
xbmc.executebuiltin("Notification(%s, %s, 7500)" % (Localization.localize('Error'), Localization.localize(
|
||||||
|
'Can\'t download torrent, probably no seeds available.')))
|
||||||
|
self.torrentFileInfo = torrentInfo
|
||||||
|
|
||||||
|
def getUploadRate(self):
|
||||||
|
if None == self.torrentHandle:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return self.torrentHandle.status().upload_payload_rate
|
||||||
|
|
||||||
|
def getDownloadRate(self):
|
||||||
|
if None == self.torrentHandle:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return self.torrentHandle.status().download_payload_rate
|
||||||
|
|
||||||
|
def getPeers(self):
|
||||||
|
if None == self.torrentHandle:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return self.torrentHandle.status().num_peers
|
||||||
|
|
||||||
|
def getSeeds(self):
|
||||||
|
if None == self.torrentHandle:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return self.torrentHandle.status().num_seeds
|
||||||
|
|
||||||
|
def getFileSize(self, contentId=0):
|
||||||
|
return self.getContentList()[contentId]['size']
|
||||||
|
|
||||||
|
def getFilePath(self, contentId=0):
|
||||||
|
return os.path.join(self.storageDirectory,self.getContentList()[contentId]['title'])#.decode('utf8')
|
||||||
|
|
||||||
|
def getContentList(self):
|
||||||
|
filelist = []
|
||||||
|
for contentId, contentFile in enumerate(self.torrentFileInfo.files()):
|
||||||
|
stringdata = {"title": contentFile.path, "size": contentFile.size, "ind": int(contentId),
|
||||||
|
'offset': contentFile.offset}
|
||||||
|
filelist.append(stringdata)
|
||||||
|
return filelist
|
||||||
|
|
||||||
|
def getSubsIds(self, filename):
|
||||||
|
subs=[]
|
||||||
|
for i in self.getContentList():
|
||||||
|
if isSubtitle(filename, i['title']):
|
||||||
|
subs.append((i['ind'], i['title']))
|
||||||
|
return subs
|
||||||
|
|
||||||
|
def setUploadLimit(self, bytesPerSecond):
|
||||||
|
self.session.set_upload_rate_limit(int(bytesPerSecond))
|
||||||
|
|
||||||
|
def setDownloadLimit(self, bytesPerSecond):
|
||||||
|
self.session.set_download_rate_limit(int(bytesPerSecond))
|
||||||
|
|
||||||
|
def md5(self, string):
|
||||||
|
hasher = hashlib.md5()
|
||||||
|
try:
|
||||||
|
hasher.update(string)
|
||||||
|
except:
|
||||||
|
hasher.update(string.encode('utf-8', 'ignore'))
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
def downloadProcess(self, contentId):
|
||||||
|
pass
|
||||||
|
#for part in range(self.startPart, self.endPart + 1):
|
||||||
|
# print 'getPiece'+str(part)
|
||||||
|
# self.getPiece(part)
|
||||||
|
# time.sleep(0.5)
|
||||||
|
# self.checkThread()
|
||||||
|
#self.threadComplete = True
|
||||||
|
|
||||||
|
def initSession(self):
|
||||||
|
try:
|
||||||
|
self.session.remove_torrent(self.torrentHandle)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.session = self.lt.session()
|
||||||
|
self.session.start_dht()
|
||||||
|
self.session.add_dht_router("router.bittorrent.com", 6881)
|
||||||
|
self.session.add_dht_router("router.utorrent.com", 6881)
|
||||||
|
self.session.add_dht_router("router.bitcomet.com", 6881)
|
||||||
|
self.session.listen_on(6881, 6891)
|
||||||
|
self.session.set_alert_mask(self.lt.alert.category_t.storage_notification)
|
||||||
|
|
||||||
|
def startSession(self):
|
||||||
|
self.initSession()
|
||||||
|
if None == self.magnetLink:
|
||||||
|
self.torrentHandle = self.session.add_torrent({'ti': self.torrentFileInfo,
|
||||||
|
'save_path': self.storageDirectory,
|
||||||
|
'flags': 0x300,
|
||||||
|
#'storage_mode': self.lt.storage_mode_t.storage_mode_allocate,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
self.torrentFileInfo = self.getMagnetInfo()
|
||||||
|
self.torrentHandle.set_sequential_download(True)
|
||||||
|
self.stopSession()
|
||||||
|
|
||||||
|
def stopSession(self):
|
||||||
|
for i in range(self.torrentFileInfo.num_pieces()):
|
||||||
|
self.torrentHandle.piece_priority(i, 0)
|
||||||
|
|
||||||
|
def continueSession(self, contentId=0, Offset=0, seeding=False):
|
||||||
|
self.piece_length = self.torrentFileInfo.piece_length()
|
||||||
|
selectedFileInfo = self.getContentList()[contentId]
|
||||||
|
if not Offset:
|
||||||
|
Offset = selectedFileInfo['size'] / (1024 * 1024)
|
||||||
|
self.partOffset = (Offset * 1024 * 1024 / self.piece_length) + 1
|
||||||
|
#print 'partOffset ' + str(self.partOffset)+str(' ')
|
||||||
|
self.startPart = selectedFileInfo['offset'] / self.piece_length
|
||||||
|
self.endPart = int((selectedFileInfo['offset'] + selectedFileInfo['size']) / self.piece_length)
|
||||||
|
#print 'part ' + str(self.startPart)+ str(' ')+ str(self.endPart)
|
||||||
|
for i in range(self.startPart, self.startPart + self.partOffset):
|
||||||
|
if i <= self.endPart:
|
||||||
|
self.torrentHandle.piece_priority(i, 7)
|
||||||
|
#print str(i)
|
||||||
|
self.torrentHandle.piece_priority(self.endPart - 1, 7)
|
||||||
|
self.torrentHandle.piece_priority(self.endPart, 7)
|
||||||
|
#thread.start_new_thread(self.checkProcess, ())
|
||||||
|
#thread.start_new_thread(self.downloadProcess, (contentId,))
|
||||||
|
if seeding:# and None == self.magnetLink:
|
||||||
|
thread.start_new_thread(self.addToSeeding, (contentId,))
|
||||||
|
|
||||||
|
def addToSeeding(self, contentId):
|
||||||
|
print 'addToSeeding!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1'
|
||||||
|
if self.torrentHandle:
|
||||||
|
print 'addToSeeding torrentHandle OK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1'
|
||||||
|
info = self.torrentHandle.get_torrent_info()
|
||||||
|
fileSettings = {
|
||||||
|
'ti': info,
|
||||||
|
'save_path': self.storageDirectory,
|
||||||
|
'flags': 0x300
|
||||||
|
}
|
||||||
|
self.seedingHandle= self.session.add_torrent(fileSettings)
|
||||||
|
piece_length = info.piece_length()
|
||||||
|
filelist=[]
|
||||||
|
for contentId, contentFile in enumerate(info.files()):
|
||||||
|
stringdata = {"title": contentFile.path, "size": contentFile.size, "ind": int(contentId),
|
||||||
|
'offset': contentFile.offset}
|
||||||
|
filelist.append(stringdata)
|
||||||
|
selectedFileInfo = filelist[contentId]
|
||||||
|
Offset = selectedFileInfo['size'] / (1024 * 1024)
|
||||||
|
partOffset = (Offset * 1024 * 1024 / piece_length) + 1
|
||||||
|
#print 'partOffset ' + str(self.partOffset)+str(' ')
|
||||||
|
startPart = selectedFileInfo['offset'] / piece_length
|
||||||
|
endPart = int((selectedFileInfo['offset'] + selectedFileInfo['size']) / piece_length)
|
||||||
|
#print 'part ' + str(self.startPart)+ str(' ')+ str(self.endPart)
|
||||||
|
for i in range(startPart, startPart + partOffset):
|
||||||
|
if i <= endPart:
|
||||||
|
self.seedingHandle.piece_priority(i, 7)
|
||||||
|
#print str(i)
|
||||||
|
self.seedingHandle.piece_priority(endPart - 1, 7)
|
||||||
|
self.seedingHandle.piece_priority(endPart, 7)
|
||||||
|
while self.seedingHandle:
|
||||||
|
xbmc.sleep(5000)
|
||||||
|
self.debug(seeding=True)
|
||||||
|
|
||||||
|
def fetchParts(self):
|
||||||
|
priorities = self.torrentHandle.piece_priorities()
|
||||||
|
status = self.torrentHandle.status()
|
||||||
|
if len(status.pieces) == 0:
|
||||||
|
return
|
||||||
|
if priorities[self.startPart] == 0:
|
||||||
|
self.torrentHandle.piece_priority(self.startPart, 2)
|
||||||
|
for part in range(self.startPart, self.endPart + 1):
|
||||||
|
if priorities[part] == 0:
|
||||||
|
self.torrentHandle.piece_priority(part, 1)
|
||||||
|
|
||||||
|
def checkThread(self):
|
||||||
|
if self.threadComplete == True:
|
||||||
|
print 'checkThread KIIIIIIIIIIILLLLLLLLLLLLLLL'
|
||||||
|
self.session.remove_torrent(self.torrentHandle)
|
||||||
|
if self.threadSeeding == True and self.seedingHandle:
|
||||||
|
print 'Seeding KIIIIIIIIIIILLLLLLLLLLLLLLL'
|
||||||
|
self.session.remove_torrent(self.seedingHandle)
|
||||||
|
thread.exit()
|
||||||
|
|
||||||
|
def debug(self, seeding=False):
|
||||||
|
try:
|
||||||
|
#print str(self.getFilePath(0))
|
||||||
|
if seeding:
|
||||||
|
s = self.seedingHandle.status()
|
||||||
|
else:
|
||||||
|
s = self.torrentHandle.status()
|
||||||
|
#get_settings=self.torrentHandle.status
|
||||||
|
#print s.num_pieces
|
||||||
|
#priorities = self.torrentHandle.piece_priorities()
|
||||||
|
#self.dump(priorities)
|
||||||
|
#print str('anonymous_mode '+str(get_settings['anonymous_mode']))
|
||||||
|
|
||||||
|
state_str = ['queued', 'checking', 'downloading metadata',
|
||||||
|
'downloading', 'finished', 'seeding', 'allocating']
|
||||||
|
print '[%s;%s] %.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s' % \
|
||||||
|
(self.lt.version, str(seeding), s.progress * 100, s.download_rate / 1000,
|
||||||
|
s.upload_rate / 1000,
|
||||||
|
s.num_peers, state_str[s.state])
|
||||||
|
i = 0
|
||||||
|
#for t in s.pieces:
|
||||||
|
# if t: i=i+1
|
||||||
|
#print str(self.session.pop_alert())
|
||||||
|
#print str(s.pieces[self.startPart:self.endPart])
|
||||||
|
#print 'True pieces: %d' % i
|
||||||
|
#print s.current_tracker
|
||||||
|
#print str(s.pieces)
|
||||||
|
except:
|
||||||
|
print 'debug error'
|
||||||
|
pass
|
||||||
|
|
||||||
|
def dump(self, obj):
|
||||||
|
for attr in dir(obj):
|
||||||
|
try:
|
||||||
|
print "'%s':'%s'," % (attr, getattr(obj, attr))
|
||||||
|
except:
|
||||||
|
pass
|
|
@ -0,0 +1,207 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
__settings__ = xbmcaddon.Addon(id='plugin.video.torrenter')
|
||||||
|
|
||||||
|
language = ('en', 'ru')[int(__settings__.getSetting("language"))]
|
||||||
|
except:
|
||||||
|
language = 'ru'
|
||||||
|
|
||||||
|
dictionary = {
|
||||||
|
'ru': {
|
||||||
|
'Seeds searching.': 'Идёт поиск сидов.',
|
||||||
|
'Please Wait': 'Подождите',
|
||||||
|
'Information': 'Информация',
|
||||||
|
'Torrent downloading is stopped.': 'Загрузка торрента прекращена.',
|
||||||
|
'Search': 'Поиск',
|
||||||
|
'Seeds': 'Сиды',
|
||||||
|
'Peers': 'Пиры',
|
||||||
|
'Materials are loading now.': 'Идёт загрузка материалов.',
|
||||||
|
'Search Phrase': 'Фраза для поиска',
|
||||||
|
'Magnet-link is converting.': 'Идёт преобразование magnet-ссылки.',
|
||||||
|
'Error': 'Ошибка',
|
||||||
|
'Your library out of date and can\'t save magnet-links.': 'Ваша библиотека устарела и не может сохранять магнет-ссылки.',
|
||||||
|
'Bookmarks': 'Закладки',
|
||||||
|
'Logout': 'Выход',
|
||||||
|
'Login': 'Вход',
|
||||||
|
'Recent Materials': 'Свежие Материалы ',
|
||||||
|
'Register': 'Регистрация',
|
||||||
|
'Bookmark': 'Закладка',
|
||||||
|
'Item successfully added to Bookmarks': 'Элемент удачно добавлен в закладки',
|
||||||
|
'Item successfully removed from Bookmarks': 'Элемент удачно удалён из закладок',
|
||||||
|
'Bookmark not added': 'Закладка не добавлена',
|
||||||
|
'Bookmark not removed': 'Закладка не удалена',
|
||||||
|
'Add To Bookmarks': 'Добавить в закладки',
|
||||||
|
'Remove From Bookmarks': 'Удалить из Закладок',
|
||||||
|
'Auth': 'Авторизация',
|
||||||
|
'Already logged in': 'Пользователь уже в системе',
|
||||||
|
'Input Email (for password recovery):': 'Введите E-mail (для восстановления пароля):',
|
||||||
|
'Input Email:': 'Введите E-mail:',
|
||||||
|
'Input Password (6+ symbols):': 'Введите пароль (6+ символов):',
|
||||||
|
'Input Password:': 'Введите пароль:',
|
||||||
|
'Login successfull': 'Вход выполнен успешно',
|
||||||
|
'Login failed': 'Вход не выполнен',
|
||||||
|
'User not logged in': 'Пользователь не в системе',
|
||||||
|
'User successfully logged out': 'Пользователь успешно покинул систему',
|
||||||
|
'Preloaded: ': 'Предзагружено: ',
|
||||||
|
'Do you want to STOP torrent downloading and seeding?': 'Вы хотите остановить загрузку и раздачу торрента?',
|
||||||
|
'Torrent Downloading': 'Загрузка торрента',
|
||||||
|
'Auth expired, please relogin': 'Авторизация истекла, пожалуйста войдите снова',
|
||||||
|
'Storage': 'Хранилище',
|
||||||
|
'Storage was cleared': 'Хранилище очищено',
|
||||||
|
'Clear Storage': 'Очистить хранилище',
|
||||||
|
'Popular': 'Популярное',
|
||||||
|
'Views': 'Просмотров',
|
||||||
|
'Uploading': 'Раздача',
|
||||||
|
'Download': 'Скачать',
|
||||||
|
'Input symbols from CAPTCHA image:': 'Введите символы с картинки CAPTCHA:',
|
||||||
|
'Please, rate watched video:': 'Пожалуйста, оцените просмотренное видео:',
|
||||||
|
'Bad': 'Плохо',
|
||||||
|
'So-So': 'Такое...',
|
||||||
|
'Good': 'Отлично',
|
||||||
|
'Ratings': 'Оценки',
|
||||||
|
'Rating': 'Оценка',
|
||||||
|
'Retry': 'Повторная попытка',
|
||||||
|
'%ds has left': 'Осталось %d попыток',
|
||||||
|
'File failed to play! Do you want to RETRY and buffer more?': 'Ошибка проигрывания файла! Хотите предзагрузить больше и повторить?',
|
||||||
|
'High Priority All Files': 'Высокий Приоритет Всем Файлам',
|
||||||
|
'Skip All Files': 'Пропустить Все Файлы',
|
||||||
|
'Start': 'Пуск',
|
||||||
|
'Stop': 'Стоп',
|
||||||
|
'High Priority': 'Высокий Приоритет',
|
||||||
|
'Skip File': 'Пропустить Файл',
|
||||||
|
'Remove': 'Удалить',
|
||||||
|
'Remove with files': 'Удалить с файлами',
|
||||||
|
'Play File': 'Проиграть файл',
|
||||||
|
'Start All Files': 'Пуск Всем Файлам',
|
||||||
|
'Stop All Files': 'Стоп Всем Файлам',
|
||||||
|
'Torrent-client Browser': 'Браузер Торрент-клиента',
|
||||||
|
'Remote Torrent-client': 'Удаленный торрент-клиент',
|
||||||
|
'You didn\'t set up replacement path in setting.': 'Вы не настроили замены путей.',
|
||||||
|
'For example /media/dl_torr/ to smb://SERVER/dl_torr/. Setup now?': 'Например /media/dl_torr/ на smb://SERVER/dl_torr/. Настроить?',
|
||||||
|
'Manual Torrent-client Path Edit': 'Вручную изменить папку торрент-клиента по-умолчанию',
|
||||||
|
'Choose .torrent in video library': 'Выберите .torrent в видеобиблиотеке',
|
||||||
|
'.torrent Player': '.torrent Проигрыватель',
|
||||||
|
'Choose directory:': 'Выберите папку:',
|
||||||
|
'Starting download next episode!': 'Начинаю скачку следующего эпизода!',
|
||||||
|
'Choose in torrent-client:': 'Выберите раздачу:',
|
||||||
|
'Search Control Window': 'Окно Управления Поиском',
|
||||||
|
'Magnet-link (magnet:...)': 'Magnet-ссылка (magnet:...)',
|
||||||
|
'Not a magnet-link!': 'Не является magnet-ссылкой',
|
||||||
|
'Magnet-link Player': 'Проигрыватель Magnet-Ссылок',
|
||||||
|
'UNKNOWN STATUS': 'Неизвестное состояние',
|
||||||
|
'Checking preloaded files...': 'Проверка файлов...',
|
||||||
|
'Waiting for website response...': 'Ожидание ответа сайта...',
|
||||||
|
'Search and cache information for:': 'Поиск и кэширование информации для:',
|
||||||
|
'Open Torrent': 'Открыть Список файлов',
|
||||||
|
'Torrent list is empty.': 'Список раздач пуст.',
|
||||||
|
'Content Lists': 'Списки Медиа',
|
||||||
|
'Canceled by User': 'Отменено пользователем',
|
||||||
|
'Do you want to search and cache full metadata + arts?': 'Хотите автоматически получать мета-данные и арты?',
|
||||||
|
'This vastly decreases load speed, but you will be asked to download premade bases!': 'Это существенно снижает скорость загрузки, но Вам предложат скачать готовые базы!',
|
||||||
|
'Do you want to preload full metadata?': 'Хотите готовую загрузить базу данных?',
|
||||||
|
'It is highly recommended!': 'Настоятельно рекомендовано согласиться!',
|
||||||
|
'TV Shows': 'Сериалы',
|
||||||
|
'Cartoons': 'Мультфильмы',
|
||||||
|
'Anime': 'Аниме',
|
||||||
|
'Hot & New': 'Горячие Новинки',
|
||||||
|
'Top 250 Movies': 'Лучшие 250 фильмов',
|
||||||
|
'Top All Time': 'Лучшее за ВСЕ ВРЕМЯ',
|
||||||
|
'by Genre': 'по Жанру',
|
||||||
|
'by Year': 'по Году',
|
||||||
|
'Action': 'Боевики',
|
||||||
|
'Adventure': 'Приключения',
|
||||||
|
'Animation': 'Анимация',
|
||||||
|
'Biography': 'Биография',
|
||||||
|
'Comedy': 'Комедии',
|
||||||
|
'Crime': 'Детектив',
|
||||||
|
'Documentary': 'Документальное',
|
||||||
|
'Drama': 'Драмы',
|
||||||
|
'Family': 'Семейное',
|
||||||
|
'Fantasy': 'Фэнтази',
|
||||||
|
'Film-Noir': 'Нуар',
|
||||||
|
'History': 'Историчекие',
|
||||||
|
'Horror': 'Ужасы',
|
||||||
|
'Music': 'Музыкальные',
|
||||||
|
'Musical': 'Мьюзиклы',
|
||||||
|
'Mystery': 'Мистика',
|
||||||
|
'Romance': 'Мелодрамы',
|
||||||
|
'Sci-Fi': 'Фантастика',
|
||||||
|
'Short': 'Короткометражки',
|
||||||
|
'Sport': 'Спортивные',
|
||||||
|
'Thriller': 'Триллеры',
|
||||||
|
'War': 'Военные',
|
||||||
|
'Western': 'Вестерны',
|
||||||
|
'[B]by Site[/B]': '[B]по Сайту[/B]',
|
||||||
|
'Movies': 'Фильмы',
|
||||||
|
'Cartoons Series': 'Мультсериалы',
|
||||||
|
'Cartoons Short': 'Мультфильмы (короткометражки)',
|
||||||
|
'Male': 'Мужские',
|
||||||
|
'Female': 'Женские',
|
||||||
|
'Russia & USSR': 'Россия + СССР',
|
||||||
|
'Next Page': 'Следующая Страница',
|
||||||
|
'Previous Page': 'Предыдущая Страница',
|
||||||
|
'Russian Movies': 'Отечественные Фильмы',
|
||||||
|
'Forieng Movies': 'Зарубежные Фильмы',
|
||||||
|
'Anime Film': 'Полнометражное Аниме',
|
||||||
|
'Anime Series': 'Аниме Сериалы',
|
||||||
|
'Can\'t download torrent, probably no seeds available.': 'Не могу скачать торрент, скорее всего нет доступных сидов.',
|
||||||
|
'Personal List': 'Личный Список',
|
||||||
|
'Add to %s': 'Добавить в %s',
|
||||||
|
'Delete from %s': 'Удалить из %s',
|
||||||
|
'Added!': 'Добавлено',
|
||||||
|
'Deleted!': 'Удалено!',
|
||||||
|
'Search History': 'История Поиска',
|
||||||
|
'Favourites': 'Избранное',
|
||||||
|
'Favourites SH': 'Избранное ИП',
|
||||||
|
'Clear Search History': 'Очистить Историю Поиска',
|
||||||
|
'Clear!': 'Очищено!',
|
||||||
|
'kb/s': 'Кб/с',
|
||||||
|
'Queued': 'В очереди',
|
||||||
|
'Checking': 'Проверка',
|
||||||
|
'Downloading metadata': 'Скачивание мета-данных',
|
||||||
|
'Downloading': 'Скачивание',
|
||||||
|
'Finished': 'Окончено',
|
||||||
|
'Seeding': 'Раздача (сидирование)',
|
||||||
|
'Allocating': 'Allocating',
|
||||||
|
'Allocating file & Checking resume': 'Allocating file & Checking resume',
|
||||||
|
'For Kids': 'Детское',
|
||||||
|
'Adult': 'Эротика',
|
||||||
|
'Does not support magnet links!': 'Не поддерживает магнит-ссылки!',
|
||||||
|
'Reset All Cache DBs': 'Сбросить Базы Данных',
|
||||||
|
'[B]Search[/B]': '[B]Поиск[/B]',
|
||||||
|
'You can always restart this by deleting DBs via Context Menu': 'Вы всегда можете перезапустить этот процесс через Контекстное Меню',
|
||||||
|
'Your preloaded databases are outdated!': 'Ваши предзакаченные базы метаданных устарели!',
|
||||||
|
'Do you want to download new ones right now?': 'Хотите прямо сейчас скачать новые?',
|
||||||
|
'Individual Tracker Options':'Выбор Трекеров',
|
||||||
|
'Downloading and copy subtitles. Please wait.':'Скачиваю и копирую субтитры. Пожалуйста подождите.',
|
||||||
|
'International Check - First Run':'International Check - Первый запуск',
|
||||||
|
'Delete Russian stuff?':'Удалить русские трекеры?'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def localize(text):
|
||||||
|
try:
|
||||||
|
return dictionary[language][text]
|
||||||
|
except:
|
||||||
|
return text
|
|
@ -0,0 +1,377 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
import urllib
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import sys
|
||||||
|
from contextlib import contextmanager, closing, nested
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcgui
|
||||||
|
import Downloader
|
||||||
|
import xbmcgui
|
||||||
|
import xbmcvfs
|
||||||
|
import Localization
|
||||||
|
from functions import calculate, showMessage, clearStorage, tempdir
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = sys.modules["__main__"].__root__
|
||||||
|
RESOURCES_PATH = os.path.join(ROOT, 'resources')
|
||||||
|
TORRENT2HTTP_TIMEOUT = 20
|
||||||
|
TORRENT2HTTP_POLL = 1000
|
||||||
|
PLAYING_EVENT_INTERVAL = 60
|
||||||
|
MIN_COMPLETED_PIECES = 0.5
|
||||||
|
|
||||||
|
WINDOW_FULLSCREEN_VIDEO = 12005
|
||||||
|
|
||||||
|
XBFONT_LEFT = 0x00000000
|
||||||
|
XBFONT_RIGHT = 0x00000001
|
||||||
|
XBFONT_CENTER_X = 0x00000002
|
||||||
|
XBFONT_CENTER_Y = 0x00000004
|
||||||
|
XBFONT_TRUNCATED = 0x00000008
|
||||||
|
XBFONT_JUSTIFY = 0x00000010
|
||||||
|
|
||||||
|
STATE_STRS = [
|
||||||
|
'Queued',
|
||||||
|
'Checking',
|
||||||
|
'Downloading metadata',
|
||||||
|
'Downloading',
|
||||||
|
'Finished',
|
||||||
|
'Seeding',
|
||||||
|
'Allocating',
|
||||||
|
'Allocating file & Checking resume'
|
||||||
|
]
|
||||||
|
|
||||||
|
VIEWPORT_WIDTH = 1920.0
|
||||||
|
VIEWPORT_HEIGHT = 1088.0
|
||||||
|
OVERLAY_WIDTH = int(VIEWPORT_WIDTH * 0.7) # 70% size
|
||||||
|
OVERLAY_HEIGHT = 150
|
||||||
|
|
||||||
|
ENCRYPTION_SETTINGS = {
|
||||||
|
"Forced": 0,
|
||||||
|
"Enabled": 1,
|
||||||
|
"Disabled": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class OverlayText(object):
|
||||||
|
def __init__(self, w, h, *args, **kwargs):
|
||||||
|
self.window = xbmcgui.Window(WINDOW_FULLSCREEN_VIDEO)
|
||||||
|
viewport_w, viewport_h = self._get_skin_resolution()
|
||||||
|
# Adjust size based on viewport, we are using 1080p coordinates
|
||||||
|
w = int(w * viewport_w / VIEWPORT_WIDTH)
|
||||||
|
h = int(h * viewport_h / VIEWPORT_HEIGHT)
|
||||||
|
x = (viewport_w - w) / 2
|
||||||
|
y = (viewport_h - h) / 2
|
||||||
|
self._shown = False
|
||||||
|
self._text = ""
|
||||||
|
self._label = xbmcgui.ControlLabel(x, y, w, h, self._text, *args, **kwargs)
|
||||||
|
self._background = xbmcgui.ControlImage(x, y, w, h, os.path.join(RESOURCES_PATH, "images", "black.png"))
|
||||||
|
self._background.setColorDiffuse("0xD0000000")
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
if not self._shown:
|
||||||
|
self.window.addControls([self._background, self._label])
|
||||||
|
self._shown = True
|
||||||
|
self._background.setColorDiffuse("0xD0000000")
|
||||||
|
|
||||||
|
def hide(self):
|
||||||
|
if self._shown:
|
||||||
|
self._shown = False
|
||||||
|
self.window.removeControls([self._background, self._label])
|
||||||
|
self._background.setColorDiffuse("0xFF000000")
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
return self._text
|
||||||
|
|
||||||
|
@text.setter
|
||||||
|
def text(self, text):
|
||||||
|
self._text = text
|
||||||
|
if self._shown:
|
||||||
|
self._label.setLabel(self._text)
|
||||||
|
|
||||||
|
# This is so hackish it hurts.
|
||||||
|
def _get_skin_resolution(self):
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
skin_path = xbmc.translatePath("special://skin/")
|
||||||
|
tree = ET.parse(os.path.join(skin_path, "addon.xml"))
|
||||||
|
res = tree.findall("./extension/res")[0]
|
||||||
|
return int(res.attrib["width"]), int(res.attrib["height"])
|
||||||
|
|
||||||
|
|
||||||
|
class TorrentPlayer(xbmc.Player):
|
||||||
|
__plugin__ = sys.modules["__main__"].__plugin__
|
||||||
|
__settings__ = sys.modules["__main__"].__settings__
|
||||||
|
ROOT = sys.modules["__main__"].__root__ #.decode('utf-8').encode(sys.getfilesystemencoding())
|
||||||
|
userStorageDirectory = __settings__.getSetting("storage")
|
||||||
|
USERAGENT = "Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0"
|
||||||
|
torrentFilesDirectory = 'torrents'
|
||||||
|
debug = __settings__.getSetting('debug') == 'true'
|
||||||
|
subs_dl = __settings__.getSetting('subs_dl') == 'true'
|
||||||
|
seeding = __settings__.getSetting('keep_seeding') == 'true'
|
||||||
|
|
||||||
|
def __init__(self, torrentUrl, params={}):
|
||||||
|
if 0 == len(self.userStorageDirectory):
|
||||||
|
try:
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
except:
|
||||||
|
temp_dir = tempdir()
|
||||||
|
self.userStorageDirectory = temp_dir + os.path.sep + 'Torrenter'
|
||||||
|
else:
|
||||||
|
self.userStorageDirectory = self.userStorageDirectory + 'Torrenter'
|
||||||
|
xbmc.Player.__init__(self)
|
||||||
|
print ("[TorrentPlayer] Initalized")
|
||||||
|
self.params = params
|
||||||
|
self.get = self.params.get
|
||||||
|
self.contentId = int(self.get("url"))
|
||||||
|
try:
|
||||||
|
self.ids_video = urllib.unquote_plus(self.get("url2")).split(',')
|
||||||
|
#print str(self.ids_video)
|
||||||
|
except:
|
||||||
|
self.ids_video = None
|
||||||
|
self.torrent = Downloader.Torrent(self.userStorageDirectory, torrentUrl, self.torrentFilesDirectory).player
|
||||||
|
self.init()
|
||||||
|
self.setup_torrent()
|
||||||
|
if self.buffer():
|
||||||
|
while True:
|
||||||
|
if self.setup_play():
|
||||||
|
#print '************************************* GOING LOOP'
|
||||||
|
self.torrent.continueSession(self.contentId, seeding=self.seeding)
|
||||||
|
self.loop()
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
#print '************************************* GO NEXT?'
|
||||||
|
if self.next_dl and self.next_dling and self.next_contentId and self.iterator == 100:
|
||||||
|
self.contentId = self.next_contentId
|
||||||
|
continue
|
||||||
|
#print '************************************* NO! break'
|
||||||
|
break
|
||||||
|
|
||||||
|
self.torrent.stopSession()
|
||||||
|
self.torrent.threadComplete = True
|
||||||
|
self.torrent.checkThread()
|
||||||
|
if 'false' == self.__settings__.getSetting("keep_files"):
|
||||||
|
clearStorage(self.userStorageDirectory)
|
||||||
|
else:
|
||||||
|
showMessage(Localization.localize('Information'),
|
||||||
|
Localization.localize('Torrent downloading is stopped.'), forced=True)
|
||||||
|
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.next_dl = True if self.__settings__.getSetting('next_dl') == 'true' else False
|
||||||
|
self.next_contentId = None
|
||||||
|
self.display_name = ''
|
||||||
|
self.downloadedSize = 0
|
||||||
|
self.dialog = xbmcgui.Dialog()
|
||||||
|
self.on_playback_started = []
|
||||||
|
self.on_playback_resumed = []
|
||||||
|
self.on_playback_paused = []
|
||||||
|
self.on_playback_stopped = []
|
||||||
|
|
||||||
|
def setup_torrent(self):
|
||||||
|
self.torrent.startSession()
|
||||||
|
if 0 < int(self.__settings__.getSetting("upload_limit")):
|
||||||
|
self.torrent.setUploadLimit(int(self.__settings__.getSetting("upload_limit")) * 1000000 / 8) #MBits/second
|
||||||
|
if 0 < int(self.__settings__.getSetting("download_limit")):
|
||||||
|
self.torrent.setDownloadLimit(
|
||||||
|
int(self.__settings__.getSetting("download_limit")) * 1000000 / 8) #MBits/second
|
||||||
|
self.torrent.status = False
|
||||||
|
self.fullSize = self.torrent.getFileSize(self.contentId)
|
||||||
|
Offset = calculate(self.fullSize)
|
||||||
|
#print 'Offset: '+str(Offset)
|
||||||
|
self.torrent.threadSeeding = self.seeding
|
||||||
|
self.torrent.continueSession(self.contentId, Offset=Offset, seeding=self.seeding)
|
||||||
|
|
||||||
|
def buffer(self):
|
||||||
|
iterator = 0
|
||||||
|
progressBar = xbmcgui.DialogProgress()
|
||||||
|
progressBar.create(Localization.localize('Please Wait') + str(' [%s]' % str(self.torrent.lt.version)),
|
||||||
|
Localization.localize('Seeds searching.'))
|
||||||
|
if self.subs_dl:
|
||||||
|
subs=self.torrent.getSubsIds(os.path.basename(self.torrent.getFilePath(self.contentId)))
|
||||||
|
if len(subs)>0:
|
||||||
|
for ind, title in subs:
|
||||||
|
self.torrent.continueSession(ind)
|
||||||
|
num_pieces=int(self.torrent.torrentFileInfo.num_pieces())
|
||||||
|
while iterator < 100:
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
self.torrent.debug()
|
||||||
|
downloadedSize = self.torrent.torrentHandle.file_progress()[self.contentId]
|
||||||
|
status = self.torrent.torrentHandle.status()
|
||||||
|
iterator = int(status.progress * 100)
|
||||||
|
if status.state == 0 or (status.progress == 0 and status.num_pieces > 0):
|
||||||
|
iterator = int(status.num_pieces*100/num_pieces)
|
||||||
|
if iterator > 99: iterator = 99
|
||||||
|
progressBar.update(iterator, Localization.localize('Checking preloaded files...'), ' ', ' ')
|
||||||
|
elif status.state == 3:
|
||||||
|
dialogText = Localization.localize('Preloaded: ') + str(downloadedSize / 1024 / 1024) + ' MB / ' + str(
|
||||||
|
self.fullSize / 1024 / 1024) + ' MB'
|
||||||
|
peersText = ' [%s: %s; %s: %s]' % (
|
||||||
|
Localization.localize('Seeds'), str(self.torrent.getSeeds()), Localization.localize('Peers'),
|
||||||
|
str(self.torrent.getPeers()),)
|
||||||
|
speedsText = '%s: %s Mbit/s; %s: %s Mbit/s' % (
|
||||||
|
Localization.localize('Downloading'), str(self.torrent.getDownloadRate() * 8 / 1000000),
|
||||||
|
Localization.localize('Uploading'), str(self.torrent.getUploadRate() * 8 / 1000000))
|
||||||
|
progressBar.update(iterator, Localization.localize('Seeds searching.') + peersText, dialogText,
|
||||||
|
speedsText)
|
||||||
|
else:
|
||||||
|
progressBar.update(iterator, Localization.localize('UNKNOWN STATUS'), ' ', ' ')
|
||||||
|
if progressBar.iscanceled():
|
||||||
|
progressBar.update(0)
|
||||||
|
progressBar.close()
|
||||||
|
self.torrent.threadComplete = True
|
||||||
|
self.torrent.checkThread()
|
||||||
|
return
|
||||||
|
progressBar.update(0)
|
||||||
|
progressBar.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def setup_subs(self, label, path):
|
||||||
|
iterator=0
|
||||||
|
subs=self.torrent.getSubsIds(label)
|
||||||
|
#print str(subs)
|
||||||
|
if len(subs)>0:
|
||||||
|
showMessage(Localization.localize('Information'),
|
||||||
|
Localization.localize('Downloading and copy subtitles. Please wait.'), forced=True)
|
||||||
|
for ind, title in subs:
|
||||||
|
self.torrent.continueSession(ind)
|
||||||
|
while iterator < 100:
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
self.torrent.debug()
|
||||||
|
status = self.torrent.torrentHandle.status()
|
||||||
|
iterator = int(status.progress * 100)
|
||||||
|
xbmc.sleep(2000)
|
||||||
|
for ind, title in subs:
|
||||||
|
newFileName=os.path.join(self.userStorageDirectory,os.path.dirname(path),os.path.basename(title))
|
||||||
|
if xbmcvfs.exists(newFileName):
|
||||||
|
newFileName=None
|
||||||
|
for i in range(1,9):
|
||||||
|
temp=os.path.basename(title)
|
||||||
|
ext=temp.split('.')[-1]
|
||||||
|
temp = temp[:len(temp) - len(temp.split('.')[-1]) - 1]+'.'+str(i)+'.'+ext
|
||||||
|
if not xbmcvfs.exists(os.path.join(self.userStorageDirectory,os.path.dirname(path),temp)):
|
||||||
|
newFileName=os.path.join(self.userStorageDirectory,os.path.dirname(path),temp)
|
||||||
|
break
|
||||||
|
if newFileName:
|
||||||
|
#print str((os.path.join(self.userStorageDirectory,title),newFileName))
|
||||||
|
xbmcvfs.copy(os.path.join(self.userStorageDirectory,title),newFileName)
|
||||||
|
|
||||||
|
def setup_play(self):
|
||||||
|
self.next_dling = False
|
||||||
|
self.iterator=0
|
||||||
|
path = self.torrent.getFilePath(self.contentId)
|
||||||
|
label = os.path.basename(path)
|
||||||
|
listitem = xbmcgui.ListItem(label, path=path)
|
||||||
|
|
||||||
|
if self.subs_dl:
|
||||||
|
self.setup_subs(label, path)
|
||||||
|
|
||||||
|
if self.next_dl and self.ids_video:
|
||||||
|
next_contentId_index = self.ids_video.index(str(self.contentId)) + 1
|
||||||
|
if len(self.ids_video) > next_contentId_index:
|
||||||
|
self.next_contentId = int(self.ids_video[next_contentId_index])
|
||||||
|
else:
|
||||||
|
self.next_contentId = False
|
||||||
|
|
||||||
|
if not self.ids_video:
|
||||||
|
seasonId = self.get("seasonId")
|
||||||
|
episodeId = self.get("episodeId")
|
||||||
|
title = self.get("title")
|
||||||
|
|
||||||
|
try:
|
||||||
|
label = urllib.unquote_plus(self.get("label"))
|
||||||
|
print 'ok'
|
||||||
|
except:
|
||||||
|
print 'except'
|
||||||
|
|
||||||
|
if seasonId and episodeId and label and title:
|
||||||
|
listitem = xbmcgui.ListItem(label, path=path)
|
||||||
|
|
||||||
|
listitem.setInfo(type='video', infoLabels={'title': label,
|
||||||
|
'episode': int(episodeId),
|
||||||
|
'season': int(seasonId),
|
||||||
|
'tvshowtitle': urllib.unquote_plus(title)})
|
||||||
|
thumbnail = self.get("thumbnail")
|
||||||
|
if thumbnail:
|
||||||
|
listitem.setThumbnailImage(urllib.unquote_plus(thumbnail))
|
||||||
|
self.display_name = label
|
||||||
|
|
||||||
|
#мегакостыль!
|
||||||
|
rpc = ({'jsonrpc': '2.0', 'method': 'Files.GetDirectory', 'params': {
|
||||||
|
'media': 'video', 'directory': os.path.dirname(path)}, 'id': 0})
|
||||||
|
data = json.dumps(rpc)
|
||||||
|
request = xbmc.executeJSONRPC(data)
|
||||||
|
response = json.loads(request)
|
||||||
|
xbmc.sleep(300)
|
||||||
|
|
||||||
|
if response:
|
||||||
|
xbmc.Player().play(path, listitem)
|
||||||
|
xbmc.sleep(3000)#very important, do not edit this, podavan
|
||||||
|
return True
|
||||||
|
|
||||||
|
def onPlayBackStarted(self):
|
||||||
|
for f in self.on_playback_started:
|
||||||
|
f()
|
||||||
|
print(str(("video", "play", self.display_name)))
|
||||||
|
|
||||||
|
def onPlayBackResumed(self):
|
||||||
|
for f in self.on_playback_resumed:
|
||||||
|
f()
|
||||||
|
self.onPlayBackStarted()
|
||||||
|
|
||||||
|
def onPlayBackPaused(self):
|
||||||
|
for f in self.on_playback_paused:
|
||||||
|
f()
|
||||||
|
print(str(("video", "pause", self.display_name)))
|
||||||
|
|
||||||
|
def onPlayBackStopped(self):
|
||||||
|
for f in self.on_playback_stopped:
|
||||||
|
f()
|
||||||
|
print(str(("video", "stop", self.display_name)))
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def attach(self, callback, *events):
|
||||||
|
for event in events:
|
||||||
|
event.append(callback)
|
||||||
|
yield
|
||||||
|
for event in events:
|
||||||
|
event.remove(callback)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
with closing(
|
||||||
|
OverlayText(w=OVERLAY_WIDTH, h=OVERLAY_HEIGHT, alignment=XBFONT_CENTER_X | XBFONT_CENTER_Y)) as overlay:
|
||||||
|
with nested(self.attach(overlay.show, self.on_playback_paused),
|
||||||
|
self.attach(overlay.hide, self.on_playback_resumed, self.on_playback_stopped)):
|
||||||
|
while not xbmc.abortRequested and self.isPlaying():
|
||||||
|
self.torrent.checkThread()
|
||||||
|
self.torrent.debug()
|
||||||
|
status = self.torrent.torrentHandle.status()
|
||||||
|
overlay.text = "\n".join(self._get_status_lines(status))
|
||||||
|
#downloadedSize = torrent.torrentHandle.file_progress()[contentId]
|
||||||
|
self.iterator = int(status.progress * 100)
|
||||||
|
if self.iterator == 100 and not self.next_dling and self.next_contentId:
|
||||||
|
showMessage(Localization.localize('Torrent Downloading'),
|
||||||
|
Localization.localize('Starting download next episode!'), forced=True)
|
||||||
|
self.torrent.stopSession()
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
self.torrent.threadSeeding = self.seeding
|
||||||
|
self.torrent.continueSession(self.next_contentId, seeding=self.seeding)
|
||||||
|
|
||||||
|
path = self.torrent.getFilePath(self.next_contentId)
|
||||||
|
self.display_name = os.path.basename(path)
|
||||||
|
self.next_dling = True
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
|
||||||
|
def _get_status_lines(self, s):
|
||||||
|
return [
|
||||||
|
self.display_name.decode('utf-8'),
|
||||||
|
"%.2f%% %s" % (s.progress * 100, Localization.localize(STATE_STRS[s.state]).decode('utf-8')),
|
||||||
|
"D:%.2f%s U:%.2f%s S:%d P:%d" % (s.download_rate / 1000, Localization.localize('kb/s').decode('utf-8'),
|
||||||
|
s.upload_rate / 1000, Localization.localize('kb/s').decode('utf-8'),
|
||||||
|
s.num_seeds, s.num_peers)
|
||||||
|
]
|
|
@ -0,0 +1,80 @@
|
||||||
|
import socket
|
||||||
|
import thread
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class Proxier:
|
||||||
|
HOST = '127.0.0.1'
|
||||||
|
PORT = 51515
|
||||||
|
PATH = ''
|
||||||
|
CHUNK_SIZE = 1024
|
||||||
|
seekBytes = 0
|
||||||
|
buffering = 0
|
||||||
|
|
||||||
|
def server(self, path):
|
||||||
|
self.PATH = path
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
s.settimeout(600)
|
||||||
|
s.bind((self.HOST, self.PORT))
|
||||||
|
s.listen(1)
|
||||||
|
while True:
|
||||||
|
conn, addr = s.accept()
|
||||||
|
conn.setblocking(1)
|
||||||
|
data = conn.recv(1024)
|
||||||
|
range = re.compile('Range: bytes=(\d+)-(\d*)')
|
||||||
|
size = os.path.getsize(self.PATH)
|
||||||
|
byte1, byte2 = 0, None
|
||||||
|
|
||||||
|
if range.search(data):
|
||||||
|
g = range.search(data).groups()
|
||||||
|
if g[0]: byte1 = int(g[0])
|
||||||
|
if g[1]: byte2 = int(g[1])
|
||||||
|
|
||||||
|
length = size - byte1
|
||||||
|
if byte2 is not None:
|
||||||
|
length = byte2 - byte1
|
||||||
|
|
||||||
|
thread.start_new_thread(self.streamer, (conn, byte1, byte2, length, size, ))
|
||||||
|
|
||||||
|
def streamer(self, conn, byte1, byte2, length, size):
|
||||||
|
send = ''
|
||||||
|
if byte1 > 0 or byte2 != None:
|
||||||
|
send = send + "HTTP/1.1 206 Partial Content"
|
||||||
|
send = send + "\r\n"
|
||||||
|
send = send + "Content-Range: %s-%s/%s" % (str(byte1), str(byte1 + length - 1), str(size))
|
||||||
|
send = send + "\r\n"
|
||||||
|
self.seekBytes = byte1
|
||||||
|
else:
|
||||||
|
send = send + "HTTP/1.1 200 OK"
|
||||||
|
send = send + "\r\n"
|
||||||
|
send = send + "Content-Type: video/mp4"
|
||||||
|
send = send + "\r\n"
|
||||||
|
send = send + "Content-Length: " + str(length)
|
||||||
|
send = send + "\r\n"
|
||||||
|
send = send + "Accept-Ranges: bytes"
|
||||||
|
send = send + "\r\n"
|
||||||
|
send = send + "Connection: close"
|
||||||
|
send = send + "\r\n"
|
||||||
|
send = send + "\r\n"
|
||||||
|
conn.send(send)
|
||||||
|
fp = open(self.PATH, "rb")
|
||||||
|
fp.seek(byte1)
|
||||||
|
sent = 0
|
||||||
|
while length > sent:
|
||||||
|
chunk = fp.read(self.CHUNK_SIZE)
|
||||||
|
if chunk:
|
||||||
|
self.buffering = 0
|
||||||
|
try:
|
||||||
|
sent = sent + conn.send(chunk)
|
||||||
|
except socket.error, e:
|
||||||
|
conn.close()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.buffering = self.buffering + 1
|
||||||
|
time.sleep(1)
|
||||||
|
if self.buffering > 60:
|
||||||
|
break
|
||||||
|
fp.close()
|
||||||
|
conn.close()
|
|
@ -0,0 +1,57 @@
|
||||||
|
Official library`s website is http://www.rasterbar.com/products/libtorrent/
|
||||||
|
Plugin requires python binding
|
||||||
|
|
||||||
|
--- INSTALLATION ---
|
||||||
|
|
||||||
|
1. Windows
|
||||||
|
1.1 Download from 'http://code.google.com/p/libtorrent/downloads/list'
|
||||||
|
last version of installer for windows, at the moment
|
||||||
|
it is 'python-libtorrent-0.16.0.win32-py2.6.msi'
|
||||||
|
1.2 Run installer and point as an installation directory - directory of
|
||||||
|
python for xbmc. Usually it is 'C:\Program Files (x86)\XBMC\system\python'
|
||||||
|
1.3 Install addon and enjoy
|
||||||
|
|
||||||
|
2. Linux
|
||||||
|
2.1 Run at console 'sudo apt-get install python-libtorrent'
|
||||||
|
2.2 Install addon and enjoy
|
||||||
|
|
||||||
|
or you could compile it:
|
||||||
|
|
||||||
|
sudo apt-get install libboost-dev libboost-python-dev libboost-system-dev g++ libssl openssl autotool automake subversion
|
||||||
|
svn co https://libtorrent.svn.sourceforge.net/svnroot/libtorrent/trunk/ lt/
|
||||||
|
cd lt/
|
||||||
|
./autotool.sh
|
||||||
|
./configure
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
sudo ldconfig
|
||||||
|
|
||||||
|
________________________________________________________________________________________________________
|
||||||
|
|
||||||
|
Вебсайт библиотеки http://www.rasterbar.com/products/libtorrent/
|
||||||
|
Для работы плагина нужен её билд под python
|
||||||
|
|
||||||
|
--- ИНСТАЛЯЦИЯ ---
|
||||||
|
|
||||||
|
1. Windows
|
||||||
|
1.1 Скачиваем отсюда 'http://code.google.com/p/libtorrent/downloads/list'
|
||||||
|
последнюю версию инсталлера библиотеки для windows, на момент написания
|
||||||
|
это была версия 'python-libtorrent-0.16.0.win32-py2.6.msi'
|
||||||
|
1.2 После запуска инсталлера нужно указать в качестве директории установки -
|
||||||
|
директорию python для xbmc. Обычно это 'C:\Program Files (x86)\XBMC\system\python'
|
||||||
|
1.3 Устанавливаем аддон в XBMC и пользуемся
|
||||||
|
|
||||||
|
2. Linux
|
||||||
|
2.1 Выполняем в терминале sudo apt-get install python-libtorrent
|
||||||
|
2.2 Устанавливаем аддон в XBMC и пользуемся
|
||||||
|
|
||||||
|
или компилируем:
|
||||||
|
|
||||||
|
sudo apt-get install libboost-dev libboost-python-dev libboost-system-dev g++ libssl openssl autotool automake subversion
|
||||||
|
svn co https://libtorrent.svn.sourceforge.net/svnroot/libtorrent/trunk/ lt/
|
||||||
|
cd lt/
|
||||||
|
./autotool.sh
|
||||||
|
./configure
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
sudo ldconfig
|
|
@ -0,0 +1,66 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import xbmcgui
|
||||||
|
import Localization
|
||||||
|
|
||||||
|
|
||||||
|
class Rates(xbmcgui.Window):
|
||||||
|
buttonWidth = 250
|
||||||
|
buttonHeight = 250
|
||||||
|
width = 1280
|
||||||
|
height = 720
|
||||||
|
rate = 0
|
||||||
|
|
||||||
|
def __init__(self, *kargs):
|
||||||
|
noFocus = sys.modules["__main__"].__root__ + '/icons/bookmarks.png'
|
||||||
|
focus = sys.modules["__main__"].__root__ + '/icons/add_bookmark.png'
|
||||||
|
self.addControl(xbmcgui.ControlLabel(0, self.height / 4, self.width, 100,
|
||||||
|
Localization.localize('Please, rate watched video:'), alignment=6))
|
||||||
|
self.bad = xbmcgui.ControlButton(self.buttonWidth / 2, self.height / 2, self.buttonWidth, self.buttonHeight,
|
||||||
|
Localization.localize('Bad'), alignment=6, textColor='0xFFCC3333',
|
||||||
|
focusedColor='0xFFFF0000', focusTexture=focus, noFocusTexture=noFocus,
|
||||||
|
shadowColor='0xFF000000')
|
||||||
|
self.normal = xbmcgui.ControlButton(self.width / 2 - self.buttonWidth / 2, self.height / 2, self.buttonWidth,
|
||||||
|
self.buttonHeight, Localization.localize('So-So'), alignment=6,
|
||||||
|
textColor='0xFFCCCC33', focusedColor='0xFFFFFF00', focusTexture=focus,
|
||||||
|
noFocusTexture=noFocus, shadowColor='0xFF000000')
|
||||||
|
self.good = xbmcgui.ControlButton(self.width - self.buttonWidth * 3 / 2, self.height / 2, self.buttonWidth,
|
||||||
|
self.buttonHeight, Localization.localize('Good'), alignment=6,
|
||||||
|
textColor='0xFF33CC33', focusedColor='0xFF00FF00', focusTexture=focus,
|
||||||
|
noFocusTexture=noFocus, shadowColor='0xFF000000')
|
||||||
|
self.addControl(self.bad)
|
||||||
|
self.addControl(self.normal)
|
||||||
|
self.addControl(self.good)
|
||||||
|
self.bad.setNavigation(self.bad, self.bad, self.bad, self.normal)
|
||||||
|
self.normal.setNavigation(self.normal, self.normal, self.bad, self.good)
|
||||||
|
self.good.setNavigation(self.good, self.good, self.normal, self.good)
|
||||||
|
self.setFocus(self.normal)
|
||||||
|
|
||||||
|
def onControl(self, control):
|
||||||
|
if control == self.bad:
|
||||||
|
self.rate = -1
|
||||||
|
if control == self.normal:
|
||||||
|
self.rate = 0
|
||||||
|
if control == self.good:
|
||||||
|
self.rate = 1
|
||||||
|
self.close()
|
|
@ -0,0 +1,216 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
import cookielib
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
from StringIO import StringIO
|
||||||
|
import gzip
|
||||||
|
import socket
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import xbmcgui
|
||||||
|
import xbmc
|
||||||
|
import Localization
|
||||||
|
|
||||||
|
|
||||||
|
class SearcherABC:
|
||||||
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
|
searchIcon = '/icons/video.png'
|
||||||
|
sourceWeight = 1
|
||||||
|
cookieJar = None
|
||||||
|
timeout_multi=int(sys.modules["__main__"].__settings__.getSetting("timeout"))
|
||||||
|
|
||||||
|
socket.setdefaulttimeout(10+(10*int(timeout_multi)))
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def search(self, keyword):
|
||||||
|
'''
|
||||||
|
Retrieve keyword from the input and return a list of tuples:
|
||||||
|
filesList.append((
|
||||||
|
int(weight),
|
||||||
|
int(seeds),
|
||||||
|
str(title),
|
||||||
|
str(link),
|
||||||
|
str(image),
|
||||||
|
))
|
||||||
|
'''
|
||||||
|
return
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def isMagnetLinkSource(self):
|
||||||
|
return 'Should never see this'
|
||||||
|
|
||||||
|
def getTorrentFile(self, url):
|
||||||
|
return url
|
||||||
|
|
||||||
|
def sizeConvert(self, sizeBytes):
|
||||||
|
if long(sizeBytes) >= 1024 * 1024 * 1024:
|
||||||
|
size = str(long(sizeBytes) / (1024 * 1024 * 1024)) + 'GB'
|
||||||
|
elif long(sizeBytes) >= 1024 * 1024:
|
||||||
|
size = str(long(sizeBytes) / (1024 * 1024)) + 'MB'
|
||||||
|
elif sizeBytes >= 1024:
|
||||||
|
size = str(long(sizeBytes) / 1024) + 'KB'
|
||||||
|
else:
|
||||||
|
size = str(long(sizeBytes)) + 'B'
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
|
def check_login(self, response=None):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def login(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def load_cookie(self):
|
||||||
|
cookie=os.path.join(self.tempdir(),'cookie.txt')
|
||||||
|
self.cookieJar = cookielib.MozillaCookieJar(cookie)
|
||||||
|
if os.path.exists(cookie): self.cookieJar.load(ignore_discard=True)
|
||||||
|
|
||||||
|
def makeRequest(self, url, data={}, headers={}):
|
||||||
|
self.load_cookie()
|
||||||
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookieJar))
|
||||||
|
opener.addheaders = headers
|
||||||
|
if 0 < len(data):
|
||||||
|
encodedData = urllib.urlencode(data)
|
||||||
|
else:
|
||||||
|
encodedData = None
|
||||||
|
response = opener.open(url, encodedData)
|
||||||
|
#self.cookieJar.extract_cookies(response, urllib2)
|
||||||
|
if response.info().get('Content-Encoding') == 'gzip':
|
||||||
|
buf = StringIO(response.read())
|
||||||
|
f = gzip.GzipFile(fileobj=buf)
|
||||||
|
response = f.read()
|
||||||
|
else:
|
||||||
|
response = response.read()
|
||||||
|
return response
|
||||||
|
|
||||||
|
def askCaptcha(self, url):
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
if isinstance(temp_dir, list): temp_dir = temp_dir[0]
|
||||||
|
urllib.URLopener().retrieve(url, temp_dir + '/captcha.png')
|
||||||
|
window = xbmcgui.Window(xbmcgui.getCurrentWindowId())
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
if isinstance(temp_dir, list): temp_dir = temp_dir[0]
|
||||||
|
image = xbmcgui.ControlImage(460, 20, 360, 160, temp_dir + '/captcha.png')
|
||||||
|
window.addControl(image)
|
||||||
|
keyboardCaptcha = xbmc.Keyboard('', Localization.localize('Input symbols from CAPTCHA image:'))
|
||||||
|
keyboardCaptcha.doModal()
|
||||||
|
captchaText = keyboardCaptcha.getText()
|
||||||
|
window.removeControl(image)
|
||||||
|
if not captchaText:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return captchaText
|
||||||
|
|
||||||
|
htmlCodes = (
|
||||||
|
('&', '&'),
|
||||||
|
('<', '<'),
|
||||||
|
('>', '>'),
|
||||||
|
('"', '"'),
|
||||||
|
("'", '''),
|
||||||
|
("&", '&'),)
|
||||||
|
|
||||||
|
stripPairs = (
|
||||||
|
('<p>', '\n'),
|
||||||
|
('<li>', '\n'),
|
||||||
|
('<br>', '\n'),
|
||||||
|
('<.+?>', ' '),
|
||||||
|
('</.+?>', ' '),
|
||||||
|
(' ', ' '),
|
||||||
|
('«', '"'),
|
||||||
|
('»', '"'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def unescape(self, string):
|
||||||
|
for (symbol, code) in self.htmlCodes:
|
||||||
|
string = re.sub(code, symbol, string)
|
||||||
|
return string
|
||||||
|
|
||||||
|
def stripHtml(self, string):
|
||||||
|
for (html, replacement) in self.stripPairs:
|
||||||
|
string = re.sub(html, replacement, string)
|
||||||
|
return string
|
||||||
|
|
||||||
|
def md5(self, string):
|
||||||
|
hasher = hashlib.md5()
|
||||||
|
hasher.update(string)
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
def tempdir(self):
|
||||||
|
dirname = xbmc.translatePath('special://temp')
|
||||||
|
for subdir in ('xbmcup', 'plugin.video.torrenter'):
|
||||||
|
dirname = os.path.join(dirname, subdir)
|
||||||
|
if not os.path.exists(dirname):
|
||||||
|
os.mkdir(dirname)
|
||||||
|
return dirname
|
||||||
|
|
||||||
|
def getByLabel(self, label):
|
||||||
|
clean_label = self.clean(label)
|
||||||
|
url = 'http://ruhunt.org/feed?q=%s' % urllib.quote_plus(clean_label)
|
||||||
|
|
||||||
|
response = self.makeRequest(url)
|
||||||
|
if None != response and 0 < len(response):
|
||||||
|
#print response
|
||||||
|
try:
|
||||||
|
dat = ET.fromstring(response)
|
||||||
|
url = dat.findall('channel')[0].findall('item')[0].find('link').text
|
||||||
|
#print str(url)
|
||||||
|
response = self.makeRequest(url)
|
||||||
|
if None != response and 0 < len(response):
|
||||||
|
#print response
|
||||||
|
magnet = re.compile('<a href="(magnet.+?)">', re.DOTALL | re.MULTILINE).findall(response)[0]
|
||||||
|
return magnet
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
def timeout(self, add_seconds=0):
|
||||||
|
seconds=10+(10*int(self.timeout_multi))+int(add_seconds)
|
||||||
|
socket.setdefaulttimeout(int(seconds))
|
||||||
|
|
||||||
|
def clean(self, string):
|
||||||
|
specials = ['/', '\\', '-', '[', ']', '(', ')', ',']
|
||||||
|
for symbol in specials:
|
||||||
|
string = string.replace(symbol, ' ')
|
||||||
|
if len(string) > 120:
|
||||||
|
string = string[:120]
|
||||||
|
last_piece = string.split(' ')[-1]
|
||||||
|
string = string[:120 - len(last_piece)].strip()
|
||||||
|
return string
|
||||||
|
|
||||||
|
def saveTorrentFile(self, url, content):
|
||||||
|
try:
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
except:
|
||||||
|
temp_dir = self.tempdir()
|
||||||
|
localFileName = temp_dir + os.path.sep + self.md5(url)
|
||||||
|
|
||||||
|
localFile = open(localFileName, 'wb+')
|
||||||
|
localFile.write(content)
|
||||||
|
localFile.close()
|
||||||
|
|
||||||
|
return localFileName
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<addon id="plugin.video.torrenter" name="Torrenter" version="2.0.8" provider-name="vadim.skorba, DiMartino">
|
||||||
|
<requires>
|
||||||
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
|
<import addon="script.module.libtorrent"/>
|
||||||
|
<import addon="script.module.torrent.ts"/>
|
||||||
|
</requires>
|
||||||
|
<extension point="xbmc.python.pluginsource" provides="video" library="default.py">
|
||||||
|
<provides>video</provides>
|
||||||
|
</extension>
|
||||||
|
<extension point="xbmc.addon.metadata">
|
||||||
|
<platform>all</platform>
|
||||||
|
<summary lang='en'>Plugin helps you to watch videos from p2p torrent-networks, without full predownload.
|
||||||
|
</summary>
|
||||||
|
<description lang='en'>Plugin helps you to watch videos from p2p torrent-networks, without full predownload (uses inner python-libtorrent) or Ace Stream. It also can add, control torrents and play downloaded files with external uTorrent, Transmisson or Vuse.
|
||||||
|
</description>
|
||||||
|
<disclaimer lang='en'>GNU GPLv3 http://www.gnu.org/licenses/</disclaimer>
|
||||||
|
<summary lang='ru'>Плагин позволяет просматривать видео из пиринговых торрент-сетей, не дожидаясь полного скачивания.
|
||||||
|
</summary>
|
||||||
|
<description lang='ru'>Плагин позволяет просматривать видео из пиринговых торрент-сетей, не дожидаясь полного скачивания. Использует внутренюю библиотеку python-libtorrent или Ace Stream. Так же плагин может добавлять, проигрывать и управлять скачками в uTorrent, Transmisson и Vuse.
|
||||||
|
</description>
|
||||||
|
<disclaimer lang='ru'>GNU GPLv3 http://www.gnu.org/licenses/</disclaimer>
|
||||||
|
</extension>
|
||||||
|
</addon>
|
|
@ -0,0 +1,12 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(sys.modules["__main__"].sys.argv[0])
|
||||||
|
searcherObject = {}
|
||||||
|
searcher = 'IMDB'
|
||||||
|
if ROOT + os.sep + 'resources' + os.sep + 'contenters' not in sys.path:
|
||||||
|
sys.path.insert(0, ROOT + os.sep + 'resources' + os.sep + 'contenters')
|
||||||
|
searcherObject[searcher] = getattr(__import__(searcher), searcher)()
|
||||||
|
|
||||||
|
#print str(searcherObject[searcher].get_contentList(category='search', subcategory='Hobbit'))
|
|
@ -0,0 +1,40 @@
|
||||||
|
[B]Version 2.0.7[/B]
|
||||||
|
[+] Поиск: Добавлено несколько зарубежных трекеров
|
||||||
|
[+] Добавлена поддержка интернациональности, придется пару раз кликнуть нет
|
||||||
|
|
||||||
|
[B]Version 2.0.6[/B]
|
||||||
|
[+] Проигрыватель: Автоматическое подключение внешних субтитров
|
||||||
|
[+] Проигрыватель: Исправлено проигрывание следующего эпизода
|
||||||
|
|
||||||
|
[B]Version 2.0.5[/B]
|
||||||
|
[+] История Поиска: Избранным запросам можно отдельно указывать трекеры для поиска
|
||||||
|
[+] Оптимизирована работа с трекерами требующими авторизацию
|
||||||
|
[+] Поиск: Исправлен NNM-club, Добавлен TFileMe (возможны проблемы из-за рейтинга)
|
||||||
|
[+] Поиск: Написан KinoZalTV, но если никто не поделится для всех аккаунтом, то в общий поиск не добавлю. Однако он есть в папке \resources\searchers\unused, если переместить в \resources\searchers и изменить в коде логин и пароль, то он заработает.
|
||||||
|
[+] Исправлена работа с кириллическими путями
|
||||||
|
[+] Некоторые настройки Ace Stream перенесены в плагин
|
||||||
|
|
||||||
|
[B]Version 2.0.4[/B]
|
||||||
|
[+] Общая оптимизация
|
||||||
|
[+] Поиск: Исправлен RuTorOrg, NNM-club
|
||||||
|
[+] Списки Медиа: Улучшено распознавание метаданных
|
||||||
|
[+] Списки Медиа: Организована замена устаревших баз
|
||||||
|
[+] Списки Медиа: Новая функция - Поиск
|
||||||
|
[+] Списки Медиа: В некоторых категориях КиноПоиска добавлены страницы
|
||||||
|
[+] Добавлен вариант поиска на случай неправильного распознавания
|
||||||
|
|
||||||
|
[B]Version 2.0.3[/B]
|
||||||
|
[+] Добавлен CXZ.TO в качестве источника Списков Медиа
|
||||||
|
[+] Добавлена поддержка Ace Stream от [B]nuismons[/B]
|
||||||
|
[+] Rutor, OpenSharing, OldPirateBay переведены на .torrent с магнит-ссылок
|
||||||
|
|
||||||
|
[B]Version 2.0.2[/B]
|
||||||
|
[+] Отображение статуса загрузки при паузе
|
||||||
|
|
||||||
|
[B]Version 2.0.1[/B]
|
||||||
|
[+] Увеличен буфер для файлов более 1 ГБ
|
||||||
|
[+] Добавлен серчер OldPirateBay
|
||||||
|
[+] История поиска: Изменен порядок вывода, добавлено отключение и очищение
|
||||||
|
|
||||||
|
[B]Version 2.0.0[/B]
|
||||||
|
[+] Релиз
|
|
@ -0,0 +1,385 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import xbmcaddon
|
||||||
|
import xbmc, sys
|
||||||
|
from functions import getParameters, HistoryDB
|
||||||
|
|
||||||
|
from resources.pyxbmct.addonwindow import *
|
||||||
|
from functions import Searchers
|
||||||
|
|
||||||
|
|
||||||
|
__settings__ = xbmcaddon.Addon(id='plugin.video.torrenter')
|
||||||
|
__language__ = __settings__.getLocalizedString
|
||||||
|
__version__ = __settings__.getAddonInfo('version')
|
||||||
|
__plugin__ = __settings__.getAddonInfo('name') + " v." + __version__
|
||||||
|
__root__ = __settings__.getAddonInfo('path')
|
||||||
|
HistoryDB_name, HistoryDB_ver = 'history', 1.1
|
||||||
|
|
||||||
|
print 'SYS ARGV: ' + str(sys.argv)
|
||||||
|
|
||||||
|
if len(sys.argv)>1:
|
||||||
|
params = getParameters(sys.argv[1])
|
||||||
|
else:
|
||||||
|
params={}
|
||||||
|
|
||||||
|
class MyAddon(AddonDialogWindow):
|
||||||
|
def __init__(self, title=''):
|
||||||
|
super(MyAddon, self).__init__(title)
|
||||||
|
self.setGeometry(700, 450, 9, 4)
|
||||||
|
self.set_info_controls()
|
||||||
|
self.set_active_controls()
|
||||||
|
self.set_navigation()
|
||||||
|
# Connect a key action (Backspace) to close the window.
|
||||||
|
self.connect(ACTION_NAV_BACK, self.close)
|
||||||
|
|
||||||
|
def set_info_controls(self):
|
||||||
|
# Demo for PyXBMCt UI controls.
|
||||||
|
no_int_label = Label('Information output', alignment=ALIGN_CENTER)
|
||||||
|
self.placeControl(no_int_label, 0, 0, 1, 2)
|
||||||
|
#
|
||||||
|
label_label = Label('Label')
|
||||||
|
self.placeControl(label_label, 1, 0)
|
||||||
|
# Label
|
||||||
|
self.label = Label('Simple label')
|
||||||
|
self.placeControl(self.label, 1, 1)
|
||||||
|
#
|
||||||
|
fadelabel_label = Label('FadeLabel')
|
||||||
|
self.placeControl(fadelabel_label, 2, 0)
|
||||||
|
# FadeLabel
|
||||||
|
self.fade_label = FadeLabel()
|
||||||
|
self.placeControl(self.fade_label, 2, 1)
|
||||||
|
self.fade_label.addLabel('Very long string can be here.')
|
||||||
|
#
|
||||||
|
textbox_label = Label('TextBox')
|
||||||
|
self.placeControl(textbox_label, 3, 0)
|
||||||
|
# TextBox
|
||||||
|
self.textbox = TextBox()
|
||||||
|
self.placeControl(self.textbox, 3, 1, 2, 1)
|
||||||
|
self.textbox.setText('Text box.\nIt can contain several lines.')
|
||||||
|
#
|
||||||
|
image_label = Label('Image')
|
||||||
|
self.placeControl(image_label, 5, 0)
|
||||||
|
|
||||||
|
def set_active_controls(self):
|
||||||
|
int_label = Label('Interactive Controls', alignment=ALIGN_CENTER)
|
||||||
|
self.placeControl(int_label, 0, 2, 1, 2)
|
||||||
|
#
|
||||||
|
radiobutton_label = Label('RadioButton')
|
||||||
|
self.placeControl(radiobutton_label, 1, 2)
|
||||||
|
# RadioButton
|
||||||
|
self.radiobutton = RadioButton('Off')
|
||||||
|
self.placeControl(self.radiobutton, 1, 3)
|
||||||
|
self.connect(self.radiobutton, self.radio_update)
|
||||||
|
#
|
||||||
|
edit_label = Label('Edit')
|
||||||
|
self.placeControl(edit_label, 2, 2)
|
||||||
|
# Edit
|
||||||
|
self.edit = Edit('Edit')
|
||||||
|
self.placeControl(self.edit, 2, 3)
|
||||||
|
# Additional properties must be changed after (!) displaying a control.
|
||||||
|
self.edit.setText('Enter text here')
|
||||||
|
#
|
||||||
|
list_label = Label('List')
|
||||||
|
self.placeControl(list_label, 3, 2)
|
||||||
|
#
|
||||||
|
self.list_item_label = Label('', textColor='0xFF808080')
|
||||||
|
self.placeControl(self.list_item_label, 4, 2)
|
||||||
|
# List
|
||||||
|
self.list = List()
|
||||||
|
self.placeControl(self.list, 3, 3, 3, 1)
|
||||||
|
# Add items to the list
|
||||||
|
items = ['Item %s' % i for i in range(1, 8)]
|
||||||
|
self.list.addItems(items)
|
||||||
|
# Connect the list to a function to display which list item is selected.
|
||||||
|
self.connect(self.list, lambda: xbmc.executebuiltin('Notification(Note!,%s selected.)' %
|
||||||
|
self.list.getListItem(
|
||||||
|
self.list.getSelectedPosition()).getLabel()))
|
||||||
|
# Connect key and mouse events for list navigation feedback.
|
||||||
|
self.connectEventList(
|
||||||
|
[ACTION_MOVE_DOWN, ACTION_MOVE_UP, ACTION_MOUSE_WHEEL_DOWN, ACTION_MOUSE_WHEEL_UP, ACTION_MOUSE_MOVE],
|
||||||
|
self.list_update)
|
||||||
|
# Slider value label
|
||||||
|
SLIDER_INIT_VALUE = 25.0
|
||||||
|
self.slider_value = Label(str(SLIDER_INIT_VALUE), alignment=ALIGN_CENTER)
|
||||||
|
self.placeControl(self.slider_value, 6, 3)
|
||||||
|
#
|
||||||
|
slider_caption = Label('Slider')
|
||||||
|
self.placeControl(slider_caption, 7, 2)
|
||||||
|
# Slider
|
||||||
|
self.slider = Slider()
|
||||||
|
self.placeControl(self.slider, 7, 3, pad_y=10)
|
||||||
|
self.slider.setPercent(SLIDER_INIT_VALUE)
|
||||||
|
# Connect key and mouse events for slider update feedback.
|
||||||
|
self.connectEventList([ACTION_MOVE_LEFT, ACTION_MOVE_RIGHT, ACTION_MOUSE_DRAG], self.slider_update)
|
||||||
|
#
|
||||||
|
button_label = Label('Button')
|
||||||
|
self.placeControl(button_label, 8, 2)
|
||||||
|
# Button
|
||||||
|
self.button = Button('Close')
|
||||||
|
self.placeControl(self.button, 8, 3)
|
||||||
|
# Connect control to close the window.
|
||||||
|
self.connect(self.button, self.close)
|
||||||
|
|
||||||
|
def set_navigation(self):
|
||||||
|
# Set navigation between controls
|
||||||
|
self.button.controlUp(self.slider)
|
||||||
|
self.button.controlDown(self.radiobutton)
|
||||||
|
self.radiobutton.controlUp(self.button)
|
||||||
|
self.radiobutton.controlDown(self.edit)
|
||||||
|
self.edit.controlUp(self.radiobutton)
|
||||||
|
self.edit.controlDown(self.list)
|
||||||
|
self.list.controlUp(self.edit)
|
||||||
|
self.list.controlDown(self.slider)
|
||||||
|
self.slider.controlUp(self.list)
|
||||||
|
self.slider.controlDown(self.button)
|
||||||
|
# Set initial focus
|
||||||
|
self.setFocus(self.radiobutton)
|
||||||
|
|
||||||
|
def slider_update(self):
|
||||||
|
# Update slider value label when the slider nib moves
|
||||||
|
try:
|
||||||
|
if self.getFocus() == self.slider:
|
||||||
|
self.slider_value.setLabel('%.1f' % self.slider.getPercent())
|
||||||
|
except (RuntimeError, SystemError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def radio_update(self):
|
||||||
|
# Update radiobutton caption on toggle
|
||||||
|
if self.radiobutton.isSelected():
|
||||||
|
self.radiobutton.setLabel('On')
|
||||||
|
else:
|
||||||
|
self.radiobutton.setLabel('Off')
|
||||||
|
|
||||||
|
def list_update(self):
|
||||||
|
# Update list_item label when navigating through the list.
|
||||||
|
try:
|
||||||
|
if self.getFocus() == self.list:
|
||||||
|
self.list_item_label.setLabel(self.list.getListItem(self.list.getSelectedPosition()).getLabel())
|
||||||
|
else:
|
||||||
|
self.list_item_label.setLabel('')
|
||||||
|
except (RuntimeError, SystemError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setAnimation(self, control):
|
||||||
|
# Set fade animation for all add-on window controls
|
||||||
|
control.setAnimations([('WindowOpen', 'effect=fade start=0 end=100 time=500',),
|
||||||
|
('WindowClose', 'effect=fade start=100 end=0 time=500',)])
|
||||||
|
|
||||||
|
|
||||||
|
class ControlCenter(AddonDialogWindow):
|
||||||
|
def __init__(self, title, addtime=None):
|
||||||
|
super(ControlCenter, self).__init__(title)
|
||||||
|
|
||||||
|
self.dic = Searchers().dic()
|
||||||
|
self.db=None
|
||||||
|
self.addtime=None
|
||||||
|
self.keys = self.dic.keys()
|
||||||
|
if addtime:
|
||||||
|
self.addtime=addtime
|
||||||
|
self.db = HistoryDB(HistoryDB_name, HistoryDB_ver)
|
||||||
|
providers = self.db.get_providers(addtime)
|
||||||
|
if not providers:
|
||||||
|
self.db.set_providers(addtime, self.dic)
|
||||||
|
else:
|
||||||
|
for searcher in self.keys:
|
||||||
|
self.dic[searcher]=False
|
||||||
|
for searcher in providers:
|
||||||
|
try:
|
||||||
|
if searcher in self.keys:
|
||||||
|
self.dic[searcher]=True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.keys = self.dic.keys()
|
||||||
|
self.placed, self.button_columns, self.last_column_row = self.place()
|
||||||
|
|
||||||
|
self.setGeometry(700, 150 + 50 * self.button_columns, 3 + self.button_columns, 3)
|
||||||
|
self.set_info_controls()
|
||||||
|
self.set_active_controls()
|
||||||
|
self.set_navigation()
|
||||||
|
# Connect a key action (Backspace) to close the window.
|
||||||
|
self.connect(ACTION_NAV_BACK, self.close)
|
||||||
|
|
||||||
|
def place(self):
|
||||||
|
placed = {}
|
||||||
|
i, j = -1, 0
|
||||||
|
for item in self.keys:
|
||||||
|
if i == 2:
|
||||||
|
i = 0
|
||||||
|
j += 1
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
placed[item] = (j, i)
|
||||||
|
# print item+str((j, i))
|
||||||
|
return placed, j, i
|
||||||
|
|
||||||
|
def set_info_controls(self):
|
||||||
|
# Demo for PyXBMCt UI controls.
|
||||||
|
# no_int_label = Label(__language__(30146), alignment=ALIGN_CENTER)
|
||||||
|
#self.placeControl(no_int_label, 0, 0, 1, 3)
|
||||||
|
#
|
||||||
|
#label_timeout = Label(__language__(30410))
|
||||||
|
#self.placeControl(label_timeout, 1, 0)
|
||||||
|
# Label
|
||||||
|
#self.label = Label(__language__(30545) % TimeOut().timeout())
|
||||||
|
#self.placeControl(self.label, 1, 1)
|
||||||
|
#
|
||||||
|
#label_watched = Label(__language__(30414) % (WatchedDB().count()))
|
||||||
|
#self.placeControl(label_watched, 2, 0)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_active_controls(self):
|
||||||
|
# RadioButton
|
||||||
|
self.radiobutton = {}
|
||||||
|
self.radiobutton_top, self.radiobutton_bottom = [None, None, None], [None, None, None]
|
||||||
|
for searcher in self.keys:
|
||||||
|
place = self.placed[searcher]
|
||||||
|
self.radiobutton[searcher] = RadioButton(searcher)
|
||||||
|
self.placeControl(self.radiobutton[searcher], place[0], place[1])
|
||||||
|
self.radiobutton[searcher].setSelected(self.dic[searcher])
|
||||||
|
self.connect(self.radiobutton[searcher], self.radio_update)
|
||||||
|
if place[0] == 0:
|
||||||
|
self.radiobutton_top[place[1]] = self.radiobutton[searcher]
|
||||||
|
self.radiobutton_bottom[place[1]] = self.radiobutton[searcher]
|
||||||
|
|
||||||
|
# Button
|
||||||
|
self.button_openset = Button(__language__(30413))
|
||||||
|
self.placeControl(self.button_openset, 2 + self.button_columns, 0)
|
||||||
|
# Connect control to close the window.
|
||||||
|
self.connect(self.button_openset, self.openSettings)
|
||||||
|
|
||||||
|
# Button
|
||||||
|
self.button_utorrent = Button(__language__(30414))
|
||||||
|
self.placeControl(self.button_utorrent, 2 + self.button_columns, 1)
|
||||||
|
# Connect control to close the window.
|
||||||
|
self.connect(self.button_utorrent, self.openUtorrent)
|
||||||
|
|
||||||
|
# Button
|
||||||
|
self.button_close = Button(__language__(30412))
|
||||||
|
self.placeControl(self.button_close, 2 + self.button_columns, 2)
|
||||||
|
# Connect control to close the window.
|
||||||
|
self.connect(self.button_close, self.close)
|
||||||
|
|
||||||
|
def set_navigation(self):
|
||||||
|
# Set navigation between controls
|
||||||
|
placed_values = self.placed.values()
|
||||||
|
placed_keys = self.placed.keys()
|
||||||
|
for searcher in placed_keys:
|
||||||
|
|
||||||
|
buttons = [self.button_openset, self.button_utorrent, self.button_close]
|
||||||
|
place = self.placed[searcher]
|
||||||
|
|
||||||
|
if place[0] == 0:
|
||||||
|
self.radiobutton[searcher].controlUp(buttons[place[1]])
|
||||||
|
else:
|
||||||
|
ser = placed_keys[placed_values.index((place[0] - 1, place[1]))]
|
||||||
|
self.radiobutton[searcher].controlUp(self.radiobutton[ser])
|
||||||
|
|
||||||
|
# self.button_columns, self.last_column_row
|
||||||
|
if place[1] == 0 and place[0] == self.button_columns:
|
||||||
|
if self.last_column_row > 0:
|
||||||
|
ser = placed_keys[placed_values.index((place[0], self.last_column_row))]
|
||||||
|
else:
|
||||||
|
ser = placed_keys[placed_values.index((place[0] - 1, 2))]
|
||||||
|
elif place[1] == 0:
|
||||||
|
ser = placed_keys[placed_values.index((place[0], 2))]
|
||||||
|
else:
|
||||||
|
ser = placed_keys[placed_values.index((place[0], place[1] - 1))]
|
||||||
|
self.radiobutton[searcher].controlLeft(self.radiobutton[ser])
|
||||||
|
|
||||||
|
#print str((self.button_columns, self.last_column_row))
|
||||||
|
#print searcher
|
||||||
|
|
||||||
|
if place == (self.button_columns, self.last_column_row) and self.last_column_row < 2:
|
||||||
|
ser = placed_keys[placed_values.index((place[0] - 1, place[1] + 1))]
|
||||||
|
elif place[1] == 2:
|
||||||
|
ser = placed_keys[placed_values.index((place[0], 0))]
|
||||||
|
else:
|
||||||
|
ser = placed_keys[placed_values.index((place[0], place[1] + 1))]
|
||||||
|
self.radiobutton[searcher].controlRight(self.radiobutton[ser])
|
||||||
|
|
||||||
|
if place[0] == self.button_columns - 1 and place[1] > self.last_column_row or \
|
||||||
|
place[0] == self.button_columns:
|
||||||
|
self.radiobutton[searcher].controlDown(buttons[place[1]])
|
||||||
|
else:
|
||||||
|
ser = placed_keys[placed_values.index((place[0] + 1, place[1]))]
|
||||||
|
self.radiobutton[searcher].controlDown(self.radiobutton[ser])
|
||||||
|
|
||||||
|
self.button_openset.controlUp(self.radiobutton_bottom[0])
|
||||||
|
self.button_openset.controlDown(self.radiobutton_top[0])
|
||||||
|
self.button_openset.controlLeft(self.button_close)
|
||||||
|
self.button_openset.controlRight(self.button_utorrent)
|
||||||
|
|
||||||
|
self.button_utorrent.controlUp(self.radiobutton_bottom[1])
|
||||||
|
self.button_utorrent.controlDown(self.radiobutton_top[1])
|
||||||
|
self.button_utorrent.controlLeft(self.button_openset)
|
||||||
|
self.button_utorrent.controlRight(self.button_close)
|
||||||
|
|
||||||
|
self.button_close.controlUp(self.radiobutton_bottom[2])
|
||||||
|
self.button_close.controlDown(self.radiobutton_top[2])
|
||||||
|
self.button_close.controlLeft(self.button_utorrent)
|
||||||
|
self.button_close.controlRight(self.button_openset)
|
||||||
|
# Set initial focus
|
||||||
|
self.setFocus(self.button_close)
|
||||||
|
|
||||||
|
def openSettings(self):
|
||||||
|
__settings__.openSettings()
|
||||||
|
|
||||||
|
def openUtorrent(self):
|
||||||
|
xbmc.executebuiltin('ActivateWindow(Videos,plugin://plugin.video.torrenter/?action=uTorrentBrowser)')
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def slider_update(self):
|
||||||
|
# Update slider value label when the slider nib moves
|
||||||
|
try:
|
||||||
|
if self.getFocus() == self.slider:
|
||||||
|
self.slider_value.setLabel('%.1f' % self.slider.getPercent())
|
||||||
|
except (RuntimeError, SystemError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def radio_update(self):
|
||||||
|
# Update radiobutton caption on toggle
|
||||||
|
index = self.radiobutton.values().index(self.getFocus())
|
||||||
|
dic = Searchers().dic()
|
||||||
|
searcher = self.radiobutton.keys()[index]
|
||||||
|
if self.addtime:
|
||||||
|
self.db.change_providers(self.addtime, searcher)
|
||||||
|
else:
|
||||||
|
Searchers().setBoolSetting(searcher, not dic[searcher])
|
||||||
|
|
||||||
|
def list_update(self):
|
||||||
|
# Update list_item label when navigating through the list.
|
||||||
|
try:
|
||||||
|
if self.getFocus() == self.list:
|
||||||
|
self.list_item_label.setLabel(self.list.getListItem(self.list.getSelectedPosition()).getLabel())
|
||||||
|
else:
|
||||||
|
self.list_item_label.setLabel('')
|
||||||
|
except (RuntimeError, SystemError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setAnimation(self, control):
|
||||||
|
# Set fade animation for all add-on window controls
|
||||||
|
control.setAnimations([('WindowOpen', 'effect=fade start=0 end=100 time=500',),
|
||||||
|
('WindowClose', 'effect=fade start=100 end=0 time=500',)])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
title='Global Torrenter Control Center'
|
||||||
|
addtime=None
|
||||||
|
if params.get('title'):
|
||||||
|
title=str(params.get('title'))
|
||||||
|
addtime=str(params.get('addtime'))
|
||||||
|
|
||||||
|
window = ControlCenter(title, addtime)
|
||||||
|
window.doModal()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Exception, e:
|
||||||
|
import xbmc
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
map(xbmc.log, traceback.format_exc().split("\n"))
|
||||||
|
raise
|
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
|
||||||
|
__settings__ = xbmcaddon.Addon(id='plugin.video.torrenter')
|
||||||
|
__version__ = __settings__.getAddonInfo('version')
|
||||||
|
__plugin__ = __settings__.getAddonInfo('name') + " v." + __version__
|
||||||
|
__root__ = __settings__.getAddonInfo('path')
|
||||||
|
|
||||||
|
if (__name__ == "__main__" ):
|
||||||
|
print __plugin__
|
||||||
|
import Core
|
||||||
|
|
||||||
|
core = Core.Core()
|
||||||
|
if (not sys.argv[2]):
|
||||||
|
core.sectionMenu()
|
||||||
|
else:
|
||||||
|
params = core.getParameters(sys.argv[2])
|
||||||
|
core.executeAction(params)
|
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 238 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 78 KiB |
|
@ -0,0 +1,191 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import Content
|
||||||
|
from BeautifulSoup import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
|
def make_category_dict():
|
||||||
|
category_dict = {
|
||||||
|
'movies': ('Forieng Movies', '/films/fl_foreign_hight/?'),
|
||||||
|
'rus_movies': ('Russian Movies', '/films/fl_our_hight/?'),
|
||||||
|
'tvshows': ('TV Shows', '/serials/fl_hight/?'),
|
||||||
|
'cartoons': ('Cartoons', '/cartoons/fl_hight/?'),
|
||||||
|
'anime': ('Anime', '/cartoons/cartoon_genre/anime/?'),
|
||||||
|
'hot': ('Hot & New', '/films/fl_hight/?'),
|
||||||
|
'top': ('Top 250 Movies', '/films/fl_hight/?sort=popularity&'),
|
||||||
|
'genre': {'genre': 'by Genre',
|
||||||
|
'action': ('Action', '/films/film_genre/bojevik/?'),
|
||||||
|
'adventure': ('Adventure', '/films/film_genre/priklucheniya/?'),
|
||||||
|
'biography': ('Biography', '/films/film_genre/biografiya/?'),
|
||||||
|
'comedy': ('Comedy', '/films/film_genre/komediya/?'),
|
||||||
|
'crime': ('Crime', '/films/film_genre/detektiv/?'),
|
||||||
|
'documentary': ('Documentary', '/films/film_genre/dokumentalnyj/?'),
|
||||||
|
'drama': ('Drama', '/films/film_genre/drama/?'),
|
||||||
|
'erotika': ('Adult', '/films/film_genre/erotika/?'),
|
||||||
|
'family': ('Family', '/films/film_genre/semejnyj/?'),
|
||||||
|
'fantasy': ('Fantasy', '/films/film_genre/fentezi/?'),
|
||||||
|
'film_noir': ('Film-Noir', '/films/film_genre/nuar/?'),
|
||||||
|
'history': ('History', '/films/film_genre/istoriya/?'),
|
||||||
|
'horror': ('Horror', '/films/film_genre/uzhasy/?'),
|
||||||
|
'kids': ('For Kids', '/films/film_genre/detskij/?'),
|
||||||
|
'musical': ('Musical', '/films/film_genre/muzikl/?'),
|
||||||
|
'mystery': ('Mystery', '/films/film_genre/mistika/?'),
|
||||||
|
'romance': ('Romance', '/films/film_genre/melodrama/?'),
|
||||||
|
'sci_fi': ('Sci-Fi', '/films/film_genre/fantastika/?'),
|
||||||
|
'short': ('Short', '/films/film_genre/korotkometrazhka/?'),
|
||||||
|
'thriller': ('Thriller', '/films/film_genre/triller/?'),
|
||||||
|
'war': ('War', '/films/film_genre/vojennyj/?'),
|
||||||
|
'western': ('Western', '/films/film_genre/vestern/?'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for category in category_dict.keys():
|
||||||
|
if isinstance(category_dict.get(category), dict):
|
||||||
|
for subcategory in category_dict.get(category).keys():
|
||||||
|
if subcategory != category:
|
||||||
|
x = category_dict[category][subcategory]
|
||||||
|
category_dict[category][subcategory] = (
|
||||||
|
x[0], x[1] + 'view=list', {'page': x[1] + 'view=list&page=%d', 'increase': 1, 'second_page': 1})
|
||||||
|
if not isinstance(category_dict.get(category), dict):
|
||||||
|
x = category_dict[category]
|
||||||
|
category_dict[category] = (
|
||||||
|
x[0], x[1] + 'view=list', {'page': x[1] + 'view=list&page=%d', 'increase': 1, 'second_page': 1})
|
||||||
|
|
||||||
|
category_dict['year'] = {'year': 'by Year', }
|
||||||
|
for y in range(2015, 1970, -1):
|
||||||
|
category_dict['year'][str(y)] = (str(y), '/films/year/%s/' % str(y),
|
||||||
|
{'page': '/films/year/%s/' % str(y) + '?view=list&page=%d', 'increase': 1,
|
||||||
|
'second_page': 1})
|
||||||
|
|
||||||
|
return category_dict
|
||||||
|
|
||||||
|
|
||||||
|
class CXZ(Content.Content):
|
||||||
|
category_dict = make_category_dict()
|
||||||
|
|
||||||
|
regex_list = []
|
||||||
|
|
||||||
|
baseurl = "http://cxz.to"
|
||||||
|
headers = [('User-Agent',
|
||||||
|
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124' + \
|
||||||
|
' YaBrowser/14.10.2062.12061 Safari/537.36'),
|
||||||
|
('Referer', baseurl), ('Accept-Encoding', 'gzip'), ('Accept-Language', 'ru,en;q=0.8')]
|
||||||
|
'''
|
||||||
|
Weight of source with this searcher provided.
|
||||||
|
Will be multiplied on default weight.
|
||||||
|
Default weight is seeds number
|
||||||
|
'''
|
||||||
|
sourceWeight = 2
|
||||||
|
|
||||||
|
|
||||||
|
def isLabel(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isPages(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isSearchOption(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isScrappable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_contentList(self, category, subcategory=None, page=None):
|
||||||
|
contentList = []
|
||||||
|
url = self.get_url(category, subcategory, page, self.baseurl)
|
||||||
|
|
||||||
|
response = self.makeRequest(url, headers=self.headers)
|
||||||
|
|
||||||
|
if None != response and 0 < len(response):
|
||||||
|
#print response
|
||||||
|
if category:
|
||||||
|
contentList = self.mode(response)
|
||||||
|
#print str(contentList)
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def mode(self, response):
|
||||||
|
contentList = []
|
||||||
|
Soup = BeautifulSoup(response)
|
||||||
|
result = Soup.findAll('div', {'class': 'b-poster-tile '})
|
||||||
|
num = 0
|
||||||
|
for tr in result:
|
||||||
|
#main
|
||||||
|
info = {}
|
||||||
|
year = 0
|
||||||
|
num = num + 1
|
||||||
|
title = tr.find('span', 'b-poster-tile__title-full').text.strip()
|
||||||
|
originaltitle = None
|
||||||
|
year = re.compile('(\d\d\d\d)').findall(tr.find('span', 'b-poster-tile__title-info-items').text)[0]
|
||||||
|
link = tr.find('a', 'b-poster-tile__link').get('href')
|
||||||
|
for i in ['/serials/', '/cartoonserials/', '/tvshow/']:
|
||||||
|
if i in link:
|
||||||
|
info['tvshowtitle'] = title
|
||||||
|
break
|
||||||
|
|
||||||
|
img = tr.find('img').get('src')
|
||||||
|
img = img if img else ''
|
||||||
|
|
||||||
|
#info
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (251 - int(num))),
|
||||||
|
originaltitle, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
#print result
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
- Video Values:
|
||||||
|
- genre : string (Comedy)
|
||||||
|
- year : integer (2009)
|
||||||
|
- episode : integer (4)
|
||||||
|
- season : integer (1)
|
||||||
|
- top250 : integer (192)
|
||||||
|
- rating : float (6.4) - range is 0..10
|
||||||
|
- cast : list (Michal C. Hall)
|
||||||
|
- castandrole : list (Michael C. Hall|Dexter)
|
||||||
|
- director : string (Dagur Kari)
|
||||||
|
- mpaa : string (PG-13)
|
||||||
|
- plot : string (Long Description)
|
||||||
|
- plotoutline : string (Short Description)
|
||||||
|
- title : string (Big Fan)
|
||||||
|
- originaltitle : string (Big Fan)
|
||||||
|
- sorttitle : string (Big Fan)
|
||||||
|
- duration : string (3:18)
|
||||||
|
- studio : string (Warner Bros.)
|
||||||
|
- tagline : string (An awesome movie) - short description of movie
|
||||||
|
- writer : string (Robert D. Siegel)
|
||||||
|
- tvshowtitle : string (Heroes)
|
||||||
|
- premiered : string (2005-03-04)
|
||||||
|
- status : string (Continuing) - status of a TVshow
|
||||||
|
- code : string (tt0110293) - IMDb code
|
||||||
|
- aired : string (2008-12-07)
|
||||||
|
- credits : string (Andy Kaufman) - writing credits
|
||||||
|
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||||
|
- album : string (The Joshua Tree)
|
||||||
|
- artist : list (['U2'])
|
||||||
|
- votes : string (12345 votes)
|
||||||
|
- trailer : string (/home/user/trailer.avi)
|
||||||
|
- dateadded : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||||
|
'''
|
|
@ -0,0 +1,91 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import Content, re
|
||||||
|
|
||||||
|
class EZTV(Content.Content):
|
||||||
|
category_dict = {
|
||||||
|
'hot': ('Hot & New', '/', {'page': '/page_%d', 'increase': 1, 'second_page': 1}),
|
||||||
|
}
|
||||||
|
|
||||||
|
baseurl = "https://eztv.it"
|
||||||
|
headers = [('User-Agent',
|
||||||
|
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124' + \
|
||||||
|
' YaBrowser/14.10.2062.12061 Safari/537.36'),
|
||||||
|
('Referer', 'https://eztv.it/'), ('Accept-Encoding', 'gzip')]
|
||||||
|
'''
|
||||||
|
Weight of source with this searcher provided.
|
||||||
|
Will be multiplied on default weight.
|
||||||
|
Default weight is seeds number
|
||||||
|
'''
|
||||||
|
sourceWeight = 1
|
||||||
|
|
||||||
|
def isLabel(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isScrappable(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isInfoLink(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isPages(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isSearchOption(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_contentList(self, category, subcategory=None, page=None):
|
||||||
|
contentList = []
|
||||||
|
url = self.get_url(category, subcategory, page, self.baseurl)
|
||||||
|
|
||||||
|
response = self.makeRequest(url, headers=self.headers)
|
||||||
|
|
||||||
|
if None != response and 0 < len(response):
|
||||||
|
#print response
|
||||||
|
if category in ['hot']:
|
||||||
|
contentList = self.mode(response)
|
||||||
|
#print str(contentList)
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def mode(self, response):
|
||||||
|
contentList = []
|
||||||
|
#print str(result)
|
||||||
|
num = 51
|
||||||
|
result = re.compile(
|
||||||
|
r'''class="epinfo">(.+?)</a>.+?<a href="(magnet.+?)"''',
|
||||||
|
re.DOTALL).findall(response)
|
||||||
|
for title, link in result:
|
||||||
|
#main
|
||||||
|
info = {}
|
||||||
|
num = num - 1
|
||||||
|
original_title = None
|
||||||
|
year = 0
|
||||||
|
img = ''
|
||||||
|
#info
|
||||||
|
|
||||||
|
info['label'] = info['title'] = title
|
||||||
|
info['link'] = link
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (int(num))),
|
||||||
|
original_title, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
return contentList
|
|
@ -0,0 +1,146 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import Content
|
||||||
|
from BeautifulSoup import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
|
class FastTorrent(Content.Content):
|
||||||
|
category_dict = {
|
||||||
|
#'movies':('Movies', '/popular/'),
|
||||||
|
'tvshows': (
|
||||||
|
'TV Shows', '/last-tv-torrent/', {'page': '/last-tv-torrent/%d.html', 'increase': 1, 'second_page': 2}),
|
||||||
|
'cartoons': ('Cartoons', '/last-multfilm-torrent/',
|
||||||
|
{'page': '/last-multfilm-torrent/%d.html', 'increase': 1, 'second_page': 2}),
|
||||||
|
'anime': ('Anime', '/anime/multfilm/', {'page': '/anime/multfilm/%d.html', 'increase': 1, 'second_page': 2}),
|
||||||
|
'hot': ('Hot & New', '/new-films/', {'page': '/new-films/%d.html', 'increase': 1, 'second_page': 2}),
|
||||||
|
'genre': {'genre': 'by Genre',
|
||||||
|
'amime_series': ('Anime Series', '/anime-serialy/multfilm/',
|
||||||
|
{'page': '/anime-serialy/multfilm/%d.html', 'increase': 1, 'second_page': 2}),
|
||||||
|
}
|
||||||
|
#'top':('Top 250 Movies', '/top/'),
|
||||||
|
}
|
||||||
|
baseurl = "http://www.fast-torrent.ru"
|
||||||
|
headers = [('User-Agent',
|
||||||
|
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124' + \
|
||||||
|
' YaBrowser/14.10.2062.12061 Safari/537.36'),
|
||||||
|
('Referer', baseurl), ('Accept-Encoding', 'gzip'), ('Accept-Language', 'ru,en;q=0.8')]
|
||||||
|
'''
|
||||||
|
Weight of source with this searcher provided.
|
||||||
|
Will be multiplied on default weight.
|
||||||
|
Default weight is seeds number
|
||||||
|
'''
|
||||||
|
sourceWeight = 1
|
||||||
|
|
||||||
|
def isLabel(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isScrappable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isSearchOption(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isInfoLink(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isPages(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_contentList(self, category, subcategory=None, page=None):
|
||||||
|
contentList = []
|
||||||
|
if not subcategory or subcategory == True:
|
||||||
|
get = self.category_dict[category]
|
||||||
|
else:
|
||||||
|
get = self.category_dict[category][subcategory]
|
||||||
|
|
||||||
|
if not page or page == 1:
|
||||||
|
url = self.baseurl + get[1]
|
||||||
|
else:
|
||||||
|
property = self.get_property(category, subcategory)
|
||||||
|
|
||||||
|
page_url = property['page'] % (property['second_page'] + ((page - 2) * property['increase']))
|
||||||
|
url = self.baseurl + str(page_url)
|
||||||
|
|
||||||
|
response = self.makeRequest(url, headers=self.headers)
|
||||||
|
|
||||||
|
if None != response and 0 < len(response):
|
||||||
|
#print response
|
||||||
|
if category: # in ['hot']:
|
||||||
|
contentList = self.popmode(response)
|
||||||
|
#print str(contentList)
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def popmode(self, response):
|
||||||
|
contentList = []
|
||||||
|
Soup = BeautifulSoup(response.decode('utf-8'))
|
||||||
|
result = Soup.findAll('div', {'class': 'film-wrap'})
|
||||||
|
num = 16
|
||||||
|
for tr in result:
|
||||||
|
#main
|
||||||
|
info = {}
|
||||||
|
num = num - 1
|
||||||
|
original_title = None
|
||||||
|
year = 0
|
||||||
|
h2 = tr.find('h2')
|
||||||
|
#print str(h2)
|
||||||
|
label = h2.find('span', {'itemprop': 'name'})
|
||||||
|
if label:
|
||||||
|
title = label.text
|
||||||
|
year = re.compile('\((\d\d\d\d)\)').findall(h2.text)
|
||||||
|
if year:
|
||||||
|
year = year[0]
|
||||||
|
original_title = h2.find('span', {'itemprop': 'alternativeHeadline'})
|
||||||
|
if original_title:
|
||||||
|
original_title = original_title.text
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
title, year, original_title = \
|
||||||
|
re.compile(u'<h2>(.+?) \((\d\d\d\d)\) <br.*?>\((.+?)\)<', re.DOTALL | re.I).findall(unicode(h2))[0]
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
title, year = re.compile(u'<h2>(.+?) \((\d\d\d\d)\)<', re.DOTALL | re.I).findall(unicode(h2))[0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
a = tr.find('div', 'film-image').find('a')
|
||||||
|
link = a.get('href')
|
||||||
|
img = a.get('style')
|
||||||
|
if img:
|
||||||
|
img = img.replace('background: url(', '').rstrip(')')
|
||||||
|
|
||||||
|
#info
|
||||||
|
|
||||||
|
info['label'] = title
|
||||||
|
info['link'] = link
|
||||||
|
info['title'] = title
|
||||||
|
genre = tr.find('div', 'film-genre').text
|
||||||
|
tv = [u'Зарубежный сериал', u'Русский сериал', u'Аниме сериалы', u'Мультсериалы']
|
||||||
|
for i in tv:
|
||||||
|
if re.search(i, genre):
|
||||||
|
info['tvshowtitle'] = title
|
||||||
|
info['year'] = int(year)
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (int(num))),
|
||||||
|
original_title, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
return contentList
|
|
@ -0,0 +1,248 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
import HTMLParser
|
||||||
|
|
||||||
|
import Content
|
||||||
|
from BeautifulSoup import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
|
class IMDB(Content.Content):
|
||||||
|
category_dict = {
|
||||||
|
'movies': ('Forieng Movies', '/search/title?languages=en|1&title_type=feature&sort=moviemeter,asc'),
|
||||||
|
'rus_movies': ('Russian Movies', '/search/title?languages=ru|1&title_type=feature&sort=moviemeter,asc'),
|
||||||
|
'tvshows': ('TV Shows', '/search/title?count=100&title_type=tv_series,mini_series&ref_=gnr_tv_mp'),
|
||||||
|
'cartoons': ('Cartoons', '/search/title?genres=animation&title_type=feature&sort=moviemeter,asc'),
|
||||||
|
'anime': ('Anime',
|
||||||
|
'/search/title?count=100&genres=animation&keywords=anime&num_votes=1000,&explore=title_type&ref_=gnr_kw_an'),
|
||||||
|
'hot': ('Hot & New', '/search/title?count=100&title_type=feature%2Ctv_series%2Ctv_movie&ref_=nv_ch_mm_1'),
|
||||||
|
'top': ('Top 250 Movies', '/chart/top/'),
|
||||||
|
'search': ('[B]Search[/B]', '/find?q=%s&s=tt&ttype=ft'),
|
||||||
|
'year': {'year': 'by Year', },
|
||||||
|
'genre': {'genre': 'by Genre',
|
||||||
|
'action': ('Action', '/genre/action'),
|
||||||
|
'adventure': ('Adventure', '/genre/adventure'),
|
||||||
|
'animation': ('Animation', '/genre/animation'),
|
||||||
|
'biography': ('Biography', '/genre/biography'),
|
||||||
|
'comedy': ('Comedy', '/genre/comedy'),
|
||||||
|
'crime': ('Crime', '/genre/crime'),
|
||||||
|
'documentary': ('Documentary', '/genre/documentary'),
|
||||||
|
'drama': ('Drama', '/genre/drama'),
|
||||||
|
'family': ('Family', '/genre/family'),
|
||||||
|
'fantasy': ('Fantasy', '/genre/fantasy'),
|
||||||
|
'film_noir': ('Film-Noir', '/genre/film_noir'),
|
||||||
|
'history': ('History', '/genre/history'),
|
||||||
|
'horror': ('Horror', '/genre/horror'),
|
||||||
|
'music': ('Music', '/genre/music'),
|
||||||
|
'musical': ('Musical', '/genre/musical'),
|
||||||
|
'mystery': ('Mystery', '/genre/mystery'),
|
||||||
|
'romance': ('Romance', '/genre/romance'),
|
||||||
|
'sci_fi': ('Sci-Fi', '/genre/sci_fi'),
|
||||||
|
'short': ('Short', '/genre/short'),
|
||||||
|
'sport': ('Sport', '/genre/sport'),
|
||||||
|
'thriller': ('Thriller', '/genre/thriller'),
|
||||||
|
'war': ('War', '/genre/war'),
|
||||||
|
'western': ('Western', '/genre/western'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for y in range(2015, 1970, -1):
|
||||||
|
category_dict['year'][str(y)] = (str(y), '/year/%s/' % str(y))
|
||||||
|
|
||||||
|
regex_list = []
|
||||||
|
|
||||||
|
baseurl = "http://imdb.com"
|
||||||
|
headers = [('User-Agent',
|
||||||
|
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124' + \
|
||||||
|
' YaBrowser/14.10.2062.12061 Safari/537.36'),
|
||||||
|
('Referer', baseurl), ('Accept-Encoding', 'gzip'), ('Accept-Language', 'ru,en;q=0.8')]
|
||||||
|
'''
|
||||||
|
Weight of source with this searcher provided.
|
||||||
|
Will be multiplied on default weight.
|
||||||
|
Default weight is seeds number
|
||||||
|
'''
|
||||||
|
sourceWeight = 2
|
||||||
|
|
||||||
|
def isLabel(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isPages(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isSearchOption(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isScrappable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_contentList(self, category, subcategory=None, page=None):
|
||||||
|
contentList = []
|
||||||
|
url = self.get_url(category, subcategory, page, self.baseurl)
|
||||||
|
|
||||||
|
response = self.makeRequest(url, headers=self.headers)
|
||||||
|
|
||||||
|
if None != response and 0 < len(response):
|
||||||
|
#print response
|
||||||
|
if category in ['top']:
|
||||||
|
contentList = self.topmode(response)
|
||||||
|
elif category == 'search':
|
||||||
|
contentList = self.searchmode(response)
|
||||||
|
else: #if category in ['genre']:
|
||||||
|
contentList = self.genremode(response)
|
||||||
|
#print str(contentList)
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def searchmode(self, response):
|
||||||
|
contentList = []
|
||||||
|
pars = HTMLParser.HTMLParser()
|
||||||
|
Soup = BeautifulSoup(response)
|
||||||
|
result = Soup.findAll('tr', {'class': ['findResult odd', 'findResult even']})
|
||||||
|
num = 250
|
||||||
|
for tr in result:
|
||||||
|
#main
|
||||||
|
info = {}
|
||||||
|
year = 0
|
||||||
|
num = num - 1
|
||||||
|
|
||||||
|
title = pars.unescape(tr.findAll('a')[1].text)
|
||||||
|
tdtitle = tr.find('td', 'result_text')
|
||||||
|
#print str(tdtitle.text.encode('utf-8'))
|
||||||
|
originaltitle = tr.find('i')
|
||||||
|
if originaltitle:
|
||||||
|
originaltitle = originaltitle.text
|
||||||
|
try:
|
||||||
|
year = re.compile('\((\d\d\d\d)\)').findall(tdtitle.text)[0]
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
year = re.compile('(\d\d\d\d)').findall(tdtitle.text)[0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
info['tvshowtitle'] = title
|
||||||
|
img = self.biggerImg(tr.find('img').get('src'))
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (251 - int(num))),
|
||||||
|
originaltitle, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
#print result
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def genremode(self, response):
|
||||||
|
contentList = []
|
||||||
|
pars = HTMLParser.HTMLParser()
|
||||||
|
Soup = BeautifulSoup(response)
|
||||||
|
result = Soup.findAll('tr', {'class': ['odd detailed', 'even detailed']})
|
||||||
|
for tr in result:
|
||||||
|
#main
|
||||||
|
info = {}
|
||||||
|
year = 0
|
||||||
|
tdtitle = tr.find('td', 'title')
|
||||||
|
num = tr.find('td', 'number').text.rstrip('.')
|
||||||
|
originaltitle = None
|
||||||
|
title = pars.unescape(tdtitle.find('a').text)
|
||||||
|
try:
|
||||||
|
year = re.compile('\((\d\d\d\d)\)').findall(tdtitle.find('span', 'year_type').text)[0]
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
year = re.compile('(\d\d\d\d)').findall(tdtitle.find('span', 'year_type').text)[0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
info['tvshowtitle'] = title
|
||||||
|
img = self.biggerImg(tr.find('td', 'image').find('img').get('src'))
|
||||||
|
|
||||||
|
#info
|
||||||
|
|
||||||
|
info['code'] = tr.find('span', 'wlb_wrapper').get('data-tconst')
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (251 - int(num))),
|
||||||
|
originaltitle, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
#print result
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def biggerImg(self, img):
|
||||||
|
if img and '._' in img:
|
||||||
|
img = img.split('._')[0] + '._V1_SY_CR1,0,,_AL_.jpg'
|
||||||
|
return img
|
||||||
|
|
||||||
|
def topmode(self, response):
|
||||||
|
contentList = []
|
||||||
|
Soup = BeautifulSoup(response)
|
||||||
|
result = Soup.findAll('tr', {'class': ['odd', 'even']})
|
||||||
|
for tr in result:
|
||||||
|
#main
|
||||||
|
tdtitle = tr.find('td', 'titleColumn')
|
||||||
|
num = tdtitle.find('span', {'name': 'ir'}).text.rstrip('.')
|
||||||
|
originaltitle = None
|
||||||
|
title = tdtitle.find('a').text
|
||||||
|
year = tdtitle.find('span', {'name': 'rd'}).text.rstrip(')').lstrip('(')
|
||||||
|
tdposter = tr.find('td', 'posterColumn')
|
||||||
|
img = self.biggerImg(tdposter.find('img').get('src'))
|
||||||
|
|
||||||
|
#info
|
||||||
|
info = {}
|
||||||
|
info['title'] = title
|
||||||
|
info['year'] = int(year)
|
||||||
|
info['code'] = tr.find('div', 'wlb_ribbon').get('data-tconst')
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (251 - int(num))),
|
||||||
|
originaltitle, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
#print result
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
- Video Values:
|
||||||
|
- genre : string (Comedy)
|
||||||
|
- year : integer (2009)
|
||||||
|
- episode : integer (4)
|
||||||
|
- season : integer (1)
|
||||||
|
- top250 : integer (192)
|
||||||
|
- rating : float (6.4) - range is 0..10
|
||||||
|
- cast : list (Michal C. Hall)
|
||||||
|
- castandrole : list (Michael C. Hall|Dexter)
|
||||||
|
- director : string (Dagur Kari)
|
||||||
|
- mpaa : string (PG-13)
|
||||||
|
- plot : string (Long Description)
|
||||||
|
- plotoutline : string (Short Description)
|
||||||
|
- title : string (Big Fan)
|
||||||
|
- originaltitle : string (Big Fan)
|
||||||
|
- sorttitle : string (Big Fan)
|
||||||
|
- duration : string (3:18)
|
||||||
|
- studio : string (Warner Bros.)
|
||||||
|
- tagline : string (An awesome movie) - short description of movie
|
||||||
|
- writer : string (Robert D. Siegel)
|
||||||
|
- tvshowtitle : string (Heroes)
|
||||||
|
- premiered : string (2005-03-04)
|
||||||
|
- status : string (Continuing) - status of a TVshow
|
||||||
|
- code : string (tt0110293) - IMDb code
|
||||||
|
- aired : string (2008-12-07)
|
||||||
|
- credits : string (Andy Kaufman) - writing credits
|
||||||
|
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||||
|
- album : string (The Joshua Tree)
|
||||||
|
- artist : list (['U2'])
|
||||||
|
- votes : string (12345 votes)
|
||||||
|
- trailer : string (/home/user/trailer.avi)
|
||||||
|
- dateadded : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||||
|
'''
|
|
@ -0,0 +1,310 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import Content
|
||||||
|
from BeautifulSoup import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
|
class KinoPoisk(Content.Content):
|
||||||
|
category_dict = {
|
||||||
|
'tvshows': ('TV Shows', '/top/serial/list/'),
|
||||||
|
'cartoons': ('Cartoons', '/top/id_genre/14/'),
|
||||||
|
'search': ('[B]Search[/B]', '/s/type/film/list/1/find/%s/'),
|
||||||
|
'movies': ('Forieng Movies', '/s/type/film/list/1/m_act[country]/1/m_act[type]/film/'),
|
||||||
|
'rus_movies': ('Russian Movies', '/s/type/film/list/1/m_act[country]/2/m_act[type]/film/'),
|
||||||
|
'anime': ('Anime', '/s/type/film/list/1/order/rating/m_act[genre][0]/1750/',),
|
||||||
|
'hot': ('Hot & New', '/popular/'),
|
||||||
|
'top': ('Top 250 Movies', '/top/'),
|
||||||
|
'genre': {'genre': 'by Genre',
|
||||||
|
'russia': ('Russia & USSR', '/top/rus/list/'),
|
||||||
|
'biography': ('Biography', '/s/type/film/list/1/m_act[genre][0]/22/'),
|
||||||
|
'action': ('Action', '/top/id_genre/3/'),
|
||||||
|
'thriller': ('Thriller', '/top/id_genre/4/'),
|
||||||
|
'comedy': ('Comedy', '/top/id_genre/6/'),
|
||||||
|
'drama': ('Drama', '/top/id_genre/8/'),
|
||||||
|
'romance': ('Romance', '/top/id_genre/7/'),
|
||||||
|
'horror': ('Horror', '/top/id_genre/1/'),
|
||||||
|
'sci_fi': ('Sci-Fi', '/top/id_genre/2/'),
|
||||||
|
'documentary': ('Documentary', '/top/id_genre/12/'),
|
||||||
|
'cartoonseries': ('Cartoons Series', '/top/mult_serial/list/'),
|
||||||
|
'cartoonshort': ('Cartoons Short', '/top/short_mult/list/'),
|
||||||
|
'short': ('Short', '/top/short/list/'),
|
||||||
|
'male': ('Male', '/top/sex/male/'),
|
||||||
|
'female': ('Female', '/top/sex/female/'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for category in category_dict.keys():
|
||||||
|
if isinstance(category_dict.get(category), dict):
|
||||||
|
for subcategory in category_dict.get(category).keys():
|
||||||
|
if subcategory != category:
|
||||||
|
x = category_dict[category][subcategory]
|
||||||
|
if x[1].startswith('/s/type/film/list/'):
|
||||||
|
category_dict[category][subcategory] = (x[0], x[1] + 'perpage/25/',
|
||||||
|
{'page': x[1] + 'perpage/25/page/%d/', 'increase': 1,
|
||||||
|
'second_page': 2})
|
||||||
|
if not isinstance(category_dict.get(category), dict):
|
||||||
|
x = category_dict[category]
|
||||||
|
if x[1].startswith('/s/type/film/list/'):
|
||||||
|
category_dict[category] = (
|
||||||
|
x[0], x[1] + 'perpage/25/', {'page': x[1] + 'perpage/25/page/%d/', 'increase': 1, 'second_page': 2})
|
||||||
|
|
||||||
|
category_dict['year'] = {'year': 'by Year', }
|
||||||
|
for y in range(2015, 1970, -1):
|
||||||
|
category_dict['year'][str(y)] = (str(y), '/s/type/film/list/1/m_act[year]/%s/' % str(y) + 'perpage/25/',
|
||||||
|
{'page': '/s/type/film/list/1/m_act[year]/%s/' % str(y) + 'perpage/25/page/%d/',
|
||||||
|
'increase': 1, 'second_page': 2})
|
||||||
|
|
||||||
|
baseurl = "http://www.kinopoisk.ru"
|
||||||
|
headers = [('User-Agent',
|
||||||
|
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124' + \
|
||||||
|
' YaBrowser/14.10.2062.12061 Safari/537.36'),
|
||||||
|
('Referer', baseurl), ('Accept-Encoding', 'gzip'), ('Accept-Language', 'ru,en;q=0.8')]
|
||||||
|
'''
|
||||||
|
Weight of source with this searcher provided.
|
||||||
|
Will be multiplied on default weight.
|
||||||
|
Default weight is seeds number
|
||||||
|
'''
|
||||||
|
sourceWeight = 2
|
||||||
|
|
||||||
|
def isLabel(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isPages(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isSearchOption(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isScrappable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_contentList(self, category, subcategory=None, page=None):
|
||||||
|
socket.setdefaulttimeout(15)
|
||||||
|
contentList = []
|
||||||
|
url = self.get_url(category, subcategory, page, self.baseurl)
|
||||||
|
|
||||||
|
#print url
|
||||||
|
response = self.makeRequest(url, headers=self.headers)
|
||||||
|
|
||||||
|
if None != response and 0 < len(response):
|
||||||
|
#print response
|
||||||
|
if category in ['hot']:
|
||||||
|
contentList = self.popmode(response)
|
||||||
|
elif url.startswith(self.baseurl + '/s/type/film/list/'):
|
||||||
|
contentList = self.infomode(response)
|
||||||
|
else:
|
||||||
|
contentList = self.topmode(response)
|
||||||
|
#print str(contentList)
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def stripTtl(self, title):
|
||||||
|
bad_end = [u'\(ТВ\)', u'\(сериал\)', u'\(видео\)']
|
||||||
|
for code in bad_end:
|
||||||
|
title = re.sub(u' ' + code + '$', '', title)
|
||||||
|
return title
|
||||||
|
|
||||||
|
def popmode(self, response):
|
||||||
|
contentList = []
|
||||||
|
Soup = BeautifulSoup(response)
|
||||||
|
result = Soup.find('div', 'stat').findAll('div', 'el')
|
||||||
|
#print str(result)
|
||||||
|
for tr in result:
|
||||||
|
#main
|
||||||
|
a = tr.findAll('a')
|
||||||
|
num = a[0].text
|
||||||
|
#print num
|
||||||
|
info = {}
|
||||||
|
year = 0
|
||||||
|
img = ''
|
||||||
|
originaltitle = tr.find('i')
|
||||||
|
if originaltitle:
|
||||||
|
originaltitle = self.stripHtml(self.unescape(originaltitle.text))
|
||||||
|
title = a[1].text
|
||||||
|
link = a[1].get('href')
|
||||||
|
if link:
|
||||||
|
id = re.compile('/film/(\d+)/').findall(link)
|
||||||
|
if id:
|
||||||
|
img = self.id2img(id[0])
|
||||||
|
try:
|
||||||
|
title, year = re.compile('(.+?) \((\d\d\d\d)\)', re.DOTALL).findall(a[1].text)[0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not year:
|
||||||
|
try:
|
||||||
|
title, year = re.compile('(.+?) \(.*(\d\d\d\d)').findall(a[1].text)[0]
|
||||||
|
info['tvshowtitle'] = title
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
title = self.stripHtml(self.stripTtl(title))
|
||||||
|
|
||||||
|
#info
|
||||||
|
info['title'] = title
|
||||||
|
info['year'] = int(year)
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (201 - int(num))),
|
||||||
|
originaltitle, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def topmode(self, response):
|
||||||
|
contentList = []
|
||||||
|
Soup = BeautifulSoup(response)
|
||||||
|
result = Soup.find('table', {'cellpadding': '3'}).findAll('tr')[2:]
|
||||||
|
#print str(result)
|
||||||
|
for tr in result:
|
||||||
|
#main
|
||||||
|
td = tr.findAll('td')
|
||||||
|
year = 0
|
||||||
|
info = {}
|
||||||
|
img = ''
|
||||||
|
title, originaltitle = None, None
|
||||||
|
num = td[0].text.rstrip('.')
|
||||||
|
originaltitle = tr.find('span', 'text-grey')
|
||||||
|
if originaltitle:
|
||||||
|
originaltitle = self.stripHtml(self.unescape(originaltitle.text))
|
||||||
|
a_all = tr.find('a', {'class': 'all'})
|
||||||
|
if a_all:
|
||||||
|
link = a_all.get('href')
|
||||||
|
if link:
|
||||||
|
id = re.compile('/film/(\d+)/').findall(link)
|
||||||
|
if id:
|
||||||
|
img = self.id2img(id[0])
|
||||||
|
year = re.compile('(.+) \((\d\d\d\d)\)').findall(a_all.text)
|
||||||
|
if not year:
|
||||||
|
try:
|
||||||
|
title, year = re.compile('(.+) \(.*(\d\d\d\d)').findall(a_all.text)[0]
|
||||||
|
info['tvshowtitle'] = title
|
||||||
|
except:
|
||||||
|
title = a_all.text
|
||||||
|
else:
|
||||||
|
title, year = year[0]
|
||||||
|
title = self.stripHtml(self.stripTtl(title))
|
||||||
|
|
||||||
|
#info
|
||||||
|
if originaltitle and not title:
|
||||||
|
title = originaltitle
|
||||||
|
originaltitle = None
|
||||||
|
|
||||||
|
if title:
|
||||||
|
info['title'] = title
|
||||||
|
info['year'] = int(year)
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (251 - int(num))),
|
||||||
|
originaltitle, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def infomode(self, response):
|
||||||
|
contentList = []
|
||||||
|
Soup = BeautifulSoup(response)
|
||||||
|
result = Soup.findAll('div', 'info')
|
||||||
|
#print str(result)
|
||||||
|
num = 0
|
||||||
|
for div in result:
|
||||||
|
#main
|
||||||
|
info = {}
|
||||||
|
img = ''
|
||||||
|
name = div.find('p', 'name')
|
||||||
|
title = name.find('a').text
|
||||||
|
link = name.find('a').get('href')
|
||||||
|
if link:
|
||||||
|
id = re.compile('/film/(\d+)/').findall(link)
|
||||||
|
if id:
|
||||||
|
img = self.id2img(id[0])
|
||||||
|
year = name.find('span', 'year') if name.find('span', 'year') else 0
|
||||||
|
if year:
|
||||||
|
year=year.text
|
||||||
|
ysplit = year.split(' ')
|
||||||
|
if len(ysplit) > 1: year = ysplit[0]
|
||||||
|
title = self.stripHtml(self.unescape(title))
|
||||||
|
tvshowtitle = re.compile(u'(.+?) \((.+?)\)$').findall(title)
|
||||||
|
if tvshowtitle and tvshowtitle[0][1] in [u'сериал']:
|
||||||
|
title = tvshowtitle[0][0]
|
||||||
|
info['tvshowtitle'] = title
|
||||||
|
num = num + 1
|
||||||
|
originaltitle = div.find('span', 'gray')
|
||||||
|
if originaltitle:
|
||||||
|
originaltitle = re.match('(.+?), \d', originaltitle.text)
|
||||||
|
if originaltitle:
|
||||||
|
originaltitle = self.stripHtml(self.unescape(originaltitle.group(1)))
|
||||||
|
title = self.stripTtl(title)
|
||||||
|
|
||||||
|
#info
|
||||||
|
info['title'] = title
|
||||||
|
info['year'] = int(year)
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (100 - int(num))),
|
||||||
|
originaltitle, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
|
||||||
|
def id2img(self, id):
|
||||||
|
if id:
|
||||||
|
return "http://st.kp.yandex.net/images/film_iphone/iphone360_%s.jpg" % (str(id))
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
- Video Values:
|
||||||
|
- genre : string (Comedy)
|
||||||
|
- year : integer (2009)
|
||||||
|
- episode : integer (4)
|
||||||
|
- season : integer (1)
|
||||||
|
- top250 : integer (192)
|
||||||
|
- tracknumber : integer (3)
|
||||||
|
- rating : float (6.4) - range is 0..10
|
||||||
|
- watched : depreciated - use playcount instead
|
||||||
|
- playcount : integer (2) - number of times this item has been played
|
||||||
|
- overlay : integer (2) - range is 0..8. See GUIListItem.h for values
|
||||||
|
- cast : list (Michal C. Hall)
|
||||||
|
- castandrole : list (Michael C. Hall|Dexter)
|
||||||
|
- director : string (Dagur Kari)
|
||||||
|
- mpaa : string (PG-13)
|
||||||
|
- plot : string (Long Description)
|
||||||
|
- plotoutline : string (Short Description)
|
||||||
|
- title : string (Big Fan)
|
||||||
|
- originaltitle : string (Big Fan)
|
||||||
|
- sorttitle : string (Big Fan)
|
||||||
|
- duration : string (3:18)
|
||||||
|
- studio : string (Warner Bros.)
|
||||||
|
- tagline : string (An awesome movie) - short description of movie
|
||||||
|
- writer : string (Robert D. Siegel)
|
||||||
|
- tvshowtitle : string (Heroes)
|
||||||
|
- premiered : string (2005-03-04)
|
||||||
|
- status : string (Continuing) - status of a TVshow
|
||||||
|
- code : string (tt0110293) - IMDb code
|
||||||
|
- aired : string (2008-12-07)
|
||||||
|
- credits : string (Andy Kaufman) - writing credits
|
||||||
|
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||||
|
- album : string (The Joshua Tree)
|
||||||
|
- artist : list (['U2'])
|
||||||
|
- votes : string (12345 votes)
|
||||||
|
- trailer : string (/home/user/trailer.avi)
|
||||||
|
- dateadded : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
|
||||||
|
'''
|
|
@ -0,0 +1,110 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
'''
|
||||||
|
Torrenter plugin for XBMC
|
||||||
|
Copyright (C) 2012 Vadim Skorba
|
||||||
|
vadim.skorba@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import Content
|
||||||
|
from BeautifulSoup import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
|
class RiperAM(Content.Content):
|
||||||
|
category_dict = {
|
||||||
|
#'movies':('Movies', '/popular/'),
|
||||||
|
#'tvshows':('TV Shows', '/top/serial/list/'),
|
||||||
|
#'cartoons':('Cartoons', '/top/id_genre/14/'),
|
||||||
|
#'anime':('Anime', '/search/title?count=100&genres=animation&keywords=anime&num_votes=1000,&explore=title_type&ref_=gnr_kw_an'),
|
||||||
|
'hot': ('Hot & New', '/', {'page': '/portal.php?tp=%d', 'increase': 30, 'second_page': 30}),
|
||||||
|
#'top':('Top 250 Movies', '/top/'),
|
||||||
|
}
|
||||||
|
|
||||||
|
baseurl = "http://www.riper.am"
|
||||||
|
headers = [('User-Agent',
|
||||||
|
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124' + \
|
||||||
|
' YaBrowser/14.10.2062.12061 Safari/537.36'),
|
||||||
|
('Referer', 'http://www.riper.am/'), ('Accept-Encoding', 'gzip,deflate,sdch'),
|
||||||
|
('Accept-Language', 'ru,en;q=0.8')]
|
||||||
|
'''
|
||||||
|
Weight of source with this searcher provided.
|
||||||
|
Will be multiplied on default weight.
|
||||||
|
Default weight is seeds number
|
||||||
|
'''
|
||||||
|
sourceWeight = 1
|
||||||
|
|
||||||
|
def isLabel(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isScrappable(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isInfoLink(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isPages(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isSearchOption(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_contentList(self, category, subcategory=None, page=None):
|
||||||
|
contentList = []
|
||||||
|
url = self.get_url(category, subcategory, page, self.baseurl)
|
||||||
|
|
||||||
|
response = self.makeRequest(url, headers=self.headers)
|
||||||
|
|
||||||
|
if None != response and 0 < len(response):
|
||||||
|
#print response
|
||||||
|
if category in ['hot']:
|
||||||
|
contentList = self.popmode(response)
|
||||||
|
#print str(contentList)
|
||||||
|
return contentList
|
||||||
|
|
||||||
|
def popmode(self, response):
|
||||||
|
contentList = []
|
||||||
|
Soup = BeautifulSoup(response)
|
||||||
|
result = Soup.findAll('table', 'postbody postbody_portal')
|
||||||
|
#print str(result)
|
||||||
|
num = 31
|
||||||
|
bad_forum = [u'Безопасность', u'Книги и журналы', u'Action & Shooter', u'RPG/MMORPG']
|
||||||
|
for tr in result:
|
||||||
|
#main
|
||||||
|
info = {}
|
||||||
|
forum = tr.find('div', {'style': 'height:20px;overflow:hidden;'}).find('a').text
|
||||||
|
if forum and forum in bad_forum:
|
||||||
|
continue
|
||||||
|
link = tr.find('div', {'style': 'width:200px;overflow:hidden;'}).find('a').get('href')
|
||||||
|
num = num - 1
|
||||||
|
label = tr.find('strong').text
|
||||||
|
original_title = None
|
||||||
|
year = 0
|
||||||
|
title = self.unescape(label)
|
||||||
|
img = tr.findAll('a')[0].find('img').get('src')
|
||||||
|
if img:
|
||||||
|
img = img.replace('.webp', '.jpg')
|
||||||
|
|
||||||
|
#info
|
||||||
|
|
||||||
|
info['label'] = label
|
||||||
|
info['link'] = link
|
||||||
|
info['title'] = title
|
||||||
|
info['year'] = int(year)
|
||||||
|
|
||||||
|
contentList.append((
|
||||||
|
int(int(self.sourceWeight) * (int(num))),
|
||||||
|
original_title, title, int(year), img, info,
|
||||||
|
))
|
||||||
|
return contentList
|
After Width: | Height: | Size: 95 B |
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
|
<strings>
|
||||||
|
<string id="30001">Interface Language</string>
|
||||||
|
<string id="30002">Lock Folders View Style</string>
|
||||||
|
<string id="30003">Off</string>
|
||||||
|
<string id="30004">Save Files To Folder</string>
|
||||||
|
<string id="30007">Use magnet-links</string>
|
||||||
|
<string id="30008">Keep downloaded files</string>
|
||||||
|
<string id="30009">Keep seeding of downloaded files</string>
|
||||||
|
<string id="30010">Upload speed limit MBits/sec (0 - unlimited)</string>
|
||||||
|
<string id="30011">Download speed limit MBits/sec (0 - unlimited)</string>
|
||||||
|
<string id="30012">Use only system libtorrent</string>
|
||||||
|
<string id="30013">Predownload and play next episode</string>
|
||||||
|
<string id="30014">Download metadata for Content Lists</string>
|
||||||
|
<string id="30015">Debug (Developer Mode)</string>
|
||||||
|
<string id="30016">Confluence (by slng)</string>
|
||||||
|
<string id="30017">Transperency (by slng)</string>
|
||||||
|
<string id="30018">Confluence (by DiMartino)</string>
|
||||||
|
<string id="30019">Confluence (by RussakHH)</string>
|
||||||
|
<string id="30020">Enable Search History</string>
|
||||||
|
<string id="30021">Python-Libtorrent (recommended)</string>
|
||||||
|
<string id="30022">Ace Stream (no magnets)</string>
|
||||||
|
<string id="30023">P2P Player</string>
|
||||||
|
<string id="30024">Rest Settings in Programms - AceStream Client</string>
|
||||||
|
<string id="30025">Search Timeout</string>
|
||||||
|
<string id="30026">Short (10s)</string>
|
||||||
|
<string id="30027">Normal (20s)</string>
|
||||||
|
<string id="30028">Long (30s)</string>
|
||||||
|
<string id="30029">Predownload subtitles from all folders</string>
|
||||||
|
<string id="30030">Keep seeding until Kodi restart</string>
|
||||||
|
<string id="30101">Interface</string>
|
||||||
|
<string id="30102">P2P Network</string>
|
||||||
|
<string id="50301">Save path</string>
|
||||||
|
<string id="50302">Call dialog</string>
|
||||||
|
<string id="50303">Default</string>
|
||||||
|
<string id="50304">Path</string>
|
||||||
|
<string id="50305">Create subdirectory for scrapper</string>
|
||||||
|
<string id="50312">Host</string>
|
||||||
|
<string id="50313">Port</string>
|
||||||
|
<string id="50314">URL</string>
|
||||||
|
<string id="50315">Login</string>
|
||||||
|
<string id="50316">Password</string>
|
||||||
|
<string id="50311">Torrent Client</string>
|
||||||
|
<string id="30426">Path replacement (remote only)</string>
|
||||||
|
<string id="30412">Close</string>
|
||||||
|
<string id="30413">Open Settings</string>
|
||||||
|
<string id="30414">Torrent Client Browser</string>
|
||||||
|
|
||||||
|
</strings>
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
|
<strings>
|
||||||
|
<string id="30001">Язык интерфейса</string>
|
||||||
|
<string id="30002">Удерживать стиль отображения</string>
|
||||||
|
<string id="30003">Отключено</string>
|
||||||
|
<string id="30004">Сохранять файлы в директорию</string>
|
||||||
|
<string id="30007">Использовать магнет-ссылки</string>
|
||||||
|
<string id="30008">Хранить загруженные файлы</string>
|
||||||
|
<string id="30009">Оставаться на раздаче скачанных файлов</string>
|
||||||
|
<string id="30010">Ограничить скорость раздачи МБит/сек (0 - неограничено)</string>
|
||||||
|
<string id="30011">Ограничить скорость закачки МБит/сек (0 - неограничено)</string>
|
||||||
|
<string id="30012">Использовать только пользовательский libtorrent</string>
|
||||||
|
<string id="30013">Скачать и запустить следующий эпизод</string>
|
||||||
|
<string id="30014">Загружать мета-данные для Списков Медиа</string>
|
||||||
|
<string id="30015">Дебаг (Разработчик)</string>
|
||||||
|
<string id="30016">Confluence (от slng)</string>
|
||||||
|
<string id="30017">Transperency (от slng)</string>
|
||||||
|
<string id="30018">Confluence (от DiMartino)</string>
|
||||||
|
<string id="30019">Confluence (от RussakHH)</string>
|
||||||
|
<string id="30020">Включить Историю Поиска</string>
|
||||||
|
<string id="30021">Python-Libtorrent (предпочтительно)</string>
|
||||||
|
<string id="30022">Ace Stream (без магнит-ссылок)</string>
|
||||||
|
<string id="30023">P2P Проигрыватель</string>
|
||||||
|
<string id="30024">Остальные настройки в "Программы - AceStream Client"</string>
|
||||||
|
<string id="30025">Ожидание ответа сервера при Поиске</string>
|
||||||
|
<string id="30026">Короткое (10с)</string>
|
||||||
|
<string id="30027">Среднее (20с)</string>
|
||||||
|
<string id="30028">Долгое (30с)</string>
|
||||||
|
<string id="30029">Предзакачать и подключить субтитры</string>
|
||||||
|
<string id="30030">Сидировать до полного выключения Kodi</string>
|
||||||
|
<string id="30101">Интерфейс</string>
|
||||||
|
<string id="30102">P2P Сеть</string>
|
||||||
|
<string id="50301">Директория для сохранения файлов</string>
|
||||||
|
<string id="50302">Вызывать диалог</string>
|
||||||
|
<string id="50303">Задать по умолчанию</string>
|
||||||
|
<string id="50304">Путь к директории</string>
|
||||||
|
<string id="50305">Создавать поддиректорию для скрапера</string>
|
||||||
|
<string id="50312">Хост</string>
|
||||||
|
<string id="50313">Порт</string>
|
||||||
|
<string id="50314">URL</string>
|
||||||
|
<string id="50315">Логин</string>
|
||||||
|
<string id="50316">Пароль</string>
|
||||||
|
<string id="50311">Торрент-клиент</string>
|
||||||
|
<string id="30426">Замена пути (remote only)</string>
|
||||||
|
<string id="30412">Закрыть</string>
|
||||||
|
<string id="30413">Открыть Настройки</string>
|
||||||
|
<string id="30414">Браузер Торрент-клиента</string>
|
||||||
|
|
||||||
|
<string id="30146">Статус Плагина</string>
|
||||||
|
</strings>
|
|
@ -0,0 +1,774 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# This is a "local" version of PyXBMCt to be used in standalone addons.
|
||||||
|
#
|
||||||
|
# PyXBMCt is a mini-framework for creating XBMC Python addons with arbitrary UI
|
||||||
|
# made of controls - decendants of xbmcgui.Control class.
|
||||||
|
# The framework uses image textures from XBMC Confluence skin.
|
||||||
|
#
|
||||||
|
# Licence: GPL v.3 http://www.gnu.org/licenses/gpl.html
|
||||||
|
#
|
||||||
|
## @package addonwindow
|
||||||
|
# PyXBMCt framework module
|
||||||
|
|
||||||
|
import os
|
||||||
|
import xbmc, xbmcgui, xbmcaddon
|
||||||
|
|
||||||
|
#_addon = xbmcaddon.Addon()
|
||||||
|
_images = os.path.join(os.path.dirname(__file__), 'textures', 'default')
|
||||||
|
|
||||||
|
|
||||||
|
# Text alighnment constants. Mixed variants are obtained by bit OR (|)
|
||||||
|
ALIGN_LEFT = 0
|
||||||
|
ALIGN_RIGHT = 1
|
||||||
|
ALIGN_CENTER_X = 2
|
||||||
|
ALIGN_CENTER_Y = 4
|
||||||
|
ALIGN_CENTER = 6
|
||||||
|
ALIGN_TRUNCATED = 8
|
||||||
|
ALIGN_JUSTIFY = 10
|
||||||
|
|
||||||
|
# XBMC key action codes.
|
||||||
|
# More codes at https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h
|
||||||
|
## ESC action
|
||||||
|
ACTION_PREVIOUS_MENU = 10
|
||||||
|
## Backspace action
|
||||||
|
ACTION_NAV_BACK = 92
|
||||||
|
## Left arrow key
|
||||||
|
ACTION_MOVE_LEFT = 1
|
||||||
|
## Right arrow key
|
||||||
|
ACTION_MOVE_RIGHT = 2
|
||||||
|
## Up arrow key
|
||||||
|
ACTION_MOVE_UP = 3
|
||||||
|
## Down arrow key
|
||||||
|
ACTION_MOVE_DOWN = 4
|
||||||
|
## Mouse wheel up
|
||||||
|
ACTION_MOUSE_WHEEL_UP = 104
|
||||||
|
## Mouse wheel down
|
||||||
|
ACTION_MOUSE_WHEEL_DOWN = 105
|
||||||
|
## Mouse drag
|
||||||
|
ACTION_MOUSE_DRAG = 106
|
||||||
|
## Mouse move
|
||||||
|
ACTION_MOUSE_MOVE = 107
|
||||||
|
|
||||||
|
|
||||||
|
def _set_textures(textures={}, kwargs={}):
|
||||||
|
"""Set texture arguments for controls."""
|
||||||
|
for texture in textures.keys():
|
||||||
|
try:
|
||||||
|
kwargs[texture]
|
||||||
|
except KeyError:
|
||||||
|
kwargs[texture] = textures[texture]
|
||||||
|
|
||||||
|
|
||||||
|
class AddonWindowError(Exception):
|
||||||
|
"""Custom exception."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Label(xbmcgui.ControlLabel):
|
||||||
|
"""ControlLabel class.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
label: string or unicode - text string.
|
||||||
|
font: string - font used for label text. (e.g. 'font13')
|
||||||
|
textColor: hexstring - color of enabled label's label. (e.g. '0xFFFFFFFF')
|
||||||
|
disabledColor: hexstring - color of disabled label's label. (e.g. '0xFFFF3300')
|
||||||
|
alignment: integer - alignment of label - *Note, see xbfont.h
|
||||||
|
hasPath: bool - True=stores a path / False=no path.
|
||||||
|
angle: integer - angle of control. (+ rotates CCW, - rotates CW)"
|
||||||
|
|
||||||
|
Note:
|
||||||
|
After you create the control, you need to add it to the window with placeControl().
|
||||||
|
|
||||||
|
Example:
|
||||||
|
self.label = Label('Status', angle=45)
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
return super(Label, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class FadeLabel(xbmcgui.ControlFadeLabel):
|
||||||
|
"""Control that scrolls label text.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
font: string - font used for label text. (e.g. 'font13')
|
||||||
|
textColor: hexstring - color of fadelabel's labels. (e.g. '0xFFFFFFFF')
|
||||||
|
_alignment: integer - alignment of label - *Note, see xbfont.h
|
||||||
|
|
||||||
|
Note:
|
||||||
|
After you create the control, you need to add it to the window with placeControl().
|
||||||
|
|
||||||
|
Example:
|
||||||
|
self.fadelabel = FadeLabel(textColor='0xFFFFFFFF')
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
return super(FadeLabel, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TextBox(xbmcgui.ControlTextBox):
|
||||||
|
"""ControlTextBox class.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
font: string - font used for text. (e.g. 'font13')
|
||||||
|
textColor: hexstring - color of textbox's text. (e.g. '0xFFFFFFFF')
|
||||||
|
|
||||||
|
Note:
|
||||||
|
After you create the control, you need to add it to the window with placeControl().
|
||||||
|
|
||||||
|
Example:
|
||||||
|
self.textbox = TextBox(textColor='0xFFFFFFFF')
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
return super(TextBox, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Image(xbmcgui.ControlImage):
|
||||||
|
"""ControlImage class.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
filename: string - image filename.
|
||||||
|
colorKey: hexString - (example, '0xFFFF3300')
|
||||||
|
aspectRatio: integer - (values 0 = stretch (default), 1 = scale up (crops), 2 = scale down (black bars)
|
||||||
|
colorDiffuse: hexString - (example, '0xC0FF0000' (red tint)).
|
||||||
|
|
||||||
|
Note:
|
||||||
|
After you create the control, you need to add it to the window with placeControl().
|
||||||
|
|
||||||
|
Example:
|
||||||
|
self.image = Image('d:\images\picture.jpg', aspectRatio=2)
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
return super(Image, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Button(xbmcgui.ControlButton):
|
||||||
|
"""ControlButton class.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
label: string or unicode - text string.
|
||||||
|
focusTexture: string - filename for focus texture.
|
||||||
|
noFocusTexture: string - filename for no focus texture.
|
||||||
|
textOffsetX: integer - x offset of label.
|
||||||
|
textOffsetY: integer - y offset of label.
|
||||||
|
alignment: integer - alignment of label - *Note, see xbfont.h
|
||||||
|
font: string - font used for label text. (e.g. 'font13')
|
||||||
|
textColor: hexstring - color of enabled button's label. (e.g. '0xFFFFFFFF')
|
||||||
|
disabledColor: hexstring - color of disabled button's label. (e.g. '0xFFFF3300')
|
||||||
|
angle: integer - angle of control. (+ rotates CCW, - rotates CW)
|
||||||
|
shadowColor: hexstring - color of button's label's shadow. (e.g. '0xFF000000')
|
||||||
|
focusedColor: hexstring - color of focused button's label. (e.g. '0xFF00FFFF')
|
||||||
|
|
||||||
|
Note:
|
||||||
|
After you create the control, you need to add it to the window with placeControl().
|
||||||
|
|
||||||
|
Example:
|
||||||
|
self.button = Button('Status', font='font14')
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
textures = {'focusTexture': os.path.join(_images, 'Button', 'KeyboardKey.png'),
|
||||||
|
'noFocusTexture': os.path.join(_images, 'Button', 'KeyboardKeyNF.png')}
|
||||||
|
_set_textures(textures, kwargs)
|
||||||
|
try:
|
||||||
|
kwargs['alignment']
|
||||||
|
except KeyError:
|
||||||
|
kwargs['alignment'] = ALIGN_CENTER
|
||||||
|
return super(Button, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RadioButton(xbmcgui.ControlRadioButton):
|
||||||
|
"""ControlRadioButton class.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
label: string or unicode - text string.
|
||||||
|
focusTexture: string - filename for focus texture.
|
||||||
|
noFocusTexture: string - filename for no focus texture.
|
||||||
|
textOffsetX: integer - x offset of label.
|
||||||
|
textOffsetY: integer - y offset of label.
|
||||||
|
_alignment: integer - alignment of label - *Note, see xbfont.h
|
||||||
|
font: string - font used for label text. (e.g. 'font13')
|
||||||
|
textColor: hexstring - color of enabled radio button's label. (e.g. '0xFFFFFFFF')
|
||||||
|
disabledColor: hexstring - color of disabled radio button's label. (e.g. '0xFFFF3300')
|
||||||
|
angle: integer - angle of control. (+ rotates CCW, - rotates CW)
|
||||||
|
shadowColor: hexstring - color of radio button's label's shadow. (e.g. '0xFF000000')
|
||||||
|
focusedColor: hexstring - color of focused radio button's label. (e.g. '0xFF00FFFF')
|
||||||
|
focusOnTexture: string - filename for radio focused/checked texture.
|
||||||
|
noFocusOnTexture: string - filename for radio not focused/checked texture.
|
||||||
|
focusOffTexture: string - filename for radio focused/unchecked texture.
|
||||||
|
noFocusOffTexture: string - filename for radio not focused/unchecked texture.
|
||||||
|
Note: To customize RadioButton all 4 abovementioned textures need to be provided.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
After you create the control, you need to add it to the window with placeControl().
|
||||||
|
|
||||||
|
Example:
|
||||||
|
self.radiobutton = RadioButton('Status', font='font14')
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if int(xbmc.getInfoLabel('System.BuildVersion')[:2]) >= 13:
|
||||||
|
textures = {'focusTexture': os.path.join(_images, 'RadioButton', 'MenuItemFO.png'),
|
||||||
|
'noFocusTexture': os.path.join(_images, 'RadioButton', 'MenuItemNF.png'),
|
||||||
|
'focusOnTexture': os.path.join(_images, 'RadioButton', 'radiobutton-focus.png'),
|
||||||
|
'noFocusOnTexture': os.path.join(_images, 'RadioButton', 'radiobutton-focus.png'),
|
||||||
|
'focusOffTexture': os.path.join(_images, 'RadioButton', 'radiobutton-nofocus.png'),
|
||||||
|
'noFocusOffTexture': os.path.join(_images, 'RadioButton', 'radiobutton-nofocus.png')}
|
||||||
|
else: # This is for compatibility with Frodo and earlier versions.
|
||||||
|
textures = {'focusTexture': os.path.join(_images, 'RadioButton', 'MenuItemFO.png'),
|
||||||
|
'noFocusTexture': os.path.join(_images, 'RadioButton', 'MenuItemNF.png'),
|
||||||
|
'TextureRadioFocus': os.path.join(_images, 'RadioButton', 'radiobutton-focus.png'),
|
||||||
|
'TextureRadioNoFocus': os.path.join(_images, 'RadioButton', 'radiobutton-nofocus.png')}
|
||||||
|
_set_textures(textures, kwargs)
|
||||||
|
return super(RadioButton, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Edit(xbmcgui.ControlEdit):
|
||||||
|
"""
|
||||||
|
ControlEdit class.
|
||||||
|
|
||||||
|
Edit(label[, font, textColor, disabledColor, alignment, focusTexture, noFocusTexture])
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
label : string or unicode - text string.
|
||||||
|
font : [opt] string - font used for label text. (e.g. 'font13')
|
||||||
|
textColor : [opt] hexstring - color of enabled label's label. (e.g. '0xFFFFFFFF')
|
||||||
|
disabledColor : [opt] hexstring - color of disabled label's label. (e.g. '0xFFFF3300')
|
||||||
|
_alignment : [opt] integer - alignment of label - *Note, see xbfont.h
|
||||||
|
focusTexture : [opt] string - filename for focus texture.
|
||||||
|
noFocusTexture : [opt] string - filename for no focus texture.
|
||||||
|
isPassword : [opt] bool - if true, mask text value.
|
||||||
|
|
||||||
|
*Note, You can use the above as keywords for arguments and skip certain optional arguments.
|
||||||
|
Once you use a keyword, all following arguments require the keyword.
|
||||||
|
After you create the control, you need to add it to the window with palceControl().
|
||||||
|
|
||||||
|
example:
|
||||||
|
- self.edit = Edit('Status')
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
textures = {'focusTexture': os.path.join(_images, 'Edit', 'button-focus.png'),
|
||||||
|
'noFocusTexture': os.path.join(_images, 'Edit', 'black-back2.png')}
|
||||||
|
_set_textures(textures, kwargs)
|
||||||
|
return super(Edit, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class List(xbmcgui.ControlList):
|
||||||
|
"""ControlList class.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
font: string - font used for items label. (e.g. 'font13')
|
||||||
|
textColor: hexstring - color of items label. (e.g. '0xFFFFFFFF')
|
||||||
|
buttonTexture: string - filename for no focus texture.
|
||||||
|
buttonFocusTexture: string - filename for focus texture.
|
||||||
|
selectedColor: integer - x offset of label.
|
||||||
|
_imageWidth: integer - width of items icon or thumbnail.
|
||||||
|
_imageHeight: integer - height of items icon or thumbnail.
|
||||||
|
_itemTextXOffset: integer - x offset of items label.
|
||||||
|
_itemTextYOffset: integer - y offset of items label.
|
||||||
|
_itemHeight: integer - height of items.
|
||||||
|
_space: integer - space between items.
|
||||||
|
_alignmentY: integer - Y-axis alignment of items label - *Note, see xbfont.h
|
||||||
|
|
||||||
|
Note:
|
||||||
|
After you create the control, you need to add it to the window with placeControl().
|
||||||
|
|
||||||
|
Example:
|
||||||
|
self.cList = List('font14', space=5)
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
textures = {'buttonTexture': os.path.join(_images, 'List', 'MenuItemNF.png'),
|
||||||
|
'buttonFocusTexture': os.path.join(_images, 'List', 'MenuItemFO.png')}
|
||||||
|
_set_textures(textures, kwargs)
|
||||||
|
return super(List, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Slider(xbmcgui.ControlSlider):
|
||||||
|
"""ControlSlider class.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
textureback: string - image filename.
|
||||||
|
texture: string - image filename.
|
||||||
|
texturefocus: string - image filename.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
After you create the control, you need to add it to the window with placeControl().
|
||||||
|
|
||||||
|
Example:
|
||||||
|
self.slider = Slider()
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
textures = {'textureback': os.path.join(_images, 'Slider', 'osd_slider_bg.png'),
|
||||||
|
'texture': os.path.join(_images, 'Slider', 'osd_slider_nibNF.png'),
|
||||||
|
'texturefocus': os.path.join(_images, 'Slider', 'osd_slider_nib.png')}
|
||||||
|
_set_textures(textures, kwargs)
|
||||||
|
return super(Slider, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class _AbstractWindow(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Top-level control window.
|
||||||
|
|
||||||
|
The control windows serves as a parent widget for other XBMC UI controls
|
||||||
|
much like Tkinter.Tk or PyQt QWidget class.
|
||||||
|
This is an abstract class which is not supposed to be instantiated directly
|
||||||
|
and will raise exeptions.
|
||||||
|
|
||||||
|
This class is a basic "skeleton" for a control window.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Constructor method."""
|
||||||
|
self.actions_connected = []
|
||||||
|
self.controls_connected = []
|
||||||
|
|
||||||
|
def setGeometry(self, width_, height_, rows_, columns_, pos_x=-1, pos_y=-1):
|
||||||
|
"""
|
||||||
|
Set width, height, Grid layout, and coordinates (optional) for a new control window.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
width_, height_: widgh and height of the created window.
|
||||||
|
rows_, columns_: rows and colums of the Grid layout to place controls on.
|
||||||
|
pos_x, pos_y (optional): coordinates of the top left corner of the window.
|
||||||
|
If pos_x and pos_y are not privided, the window will be placed
|
||||||
|
at the center of the screen.
|
||||||
|
Example:
|
||||||
|
self.setGeometry(400, 500, 5, 4)
|
||||||
|
"""
|
||||||
|
self.width = width_
|
||||||
|
self.height = height_
|
||||||
|
self.rows = rows_
|
||||||
|
self.columns = columns_
|
||||||
|
if pos_x > 0 and pos_y > 0:
|
||||||
|
self.x = pos_x
|
||||||
|
self.y = pos_y
|
||||||
|
else:
|
||||||
|
self.x = 640 - self.width/2
|
||||||
|
self.y = 360 - self.height/2
|
||||||
|
self.setGrid()
|
||||||
|
|
||||||
|
def setGrid(self):
|
||||||
|
"""
|
||||||
|
Set window grid layout of rows * columns.
|
||||||
|
This is a helper method not to be called directly.
|
||||||
|
"""
|
||||||
|
self.grid_x = self.x
|
||||||
|
self.grid_y = self.y
|
||||||
|
self.tile_width = self.width/self.columns
|
||||||
|
self.tile_height = self.height/self.rows
|
||||||
|
|
||||||
|
def placeControl(self, control, row, column, rowspan=1, columnspan=1, pad_x=5, pad_y=5):
|
||||||
|
"""
|
||||||
|
Place a control within the window grid layout.
|
||||||
|
|
||||||
|
pad_x, pad_y: horisontal and vertical padding for control's
|
||||||
|
size and aspect adjustments. Negative values can be used
|
||||||
|
to make a control overlap with grid cells next to it, if necessary.
|
||||||
|
Raises AddonWindowError if a grid has not yet been set.
|
||||||
|
Example:
|
||||||
|
self.placeControl(self.label, 0, 1)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
control_x = (self.grid_x + self.tile_width * column) + pad_x
|
||||||
|
control_y = (self.grid_y + self.tile_height * row) + pad_y
|
||||||
|
control_width = self.tile_width * columnspan - 2 * pad_x
|
||||||
|
control_height = self.tile_height * rowspan - 2 * pad_y
|
||||||
|
except AttributeError:
|
||||||
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
||||||
|
control.setPosition(control_x, control_y)
|
||||||
|
control.setWidth(control_width)
|
||||||
|
control.setHeight(control_height)
|
||||||
|
self.addControl(control)
|
||||||
|
self.setAnimation(control)
|
||||||
|
|
||||||
|
def getX(self):
|
||||||
|
"""Get X coordinate of the top-left corner of the window."""
|
||||||
|
try:
|
||||||
|
return self.x
|
||||||
|
except AttributeError:
|
||||||
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
||||||
|
|
||||||
|
def getY(self):
|
||||||
|
"""Get Y coordinate of the top-left corner of the window."""
|
||||||
|
try:
|
||||||
|
return self.y
|
||||||
|
except AttributeError:
|
||||||
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
||||||
|
|
||||||
|
def getWindowWidth(self):
|
||||||
|
"""Get window width."""
|
||||||
|
try:
|
||||||
|
return self.width
|
||||||
|
except AttributeError:
|
||||||
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
||||||
|
|
||||||
|
def getWindowHeight(self):
|
||||||
|
"""Get window height."""
|
||||||
|
try:
|
||||||
|
return self.height
|
||||||
|
except AttributeError:
|
||||||
|
raise AddonWindowError('Window geometry is not defined! Call setGeometry first.')
|
||||||
|
|
||||||
|
def getRows(self):
|
||||||
|
"""
|
||||||
|
Get grid rows count.
|
||||||
|
Raises AddonWindowError if a grid has not yet been set.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.rows
|
||||||
|
except AttributeError:
|
||||||
|
raise AddonWindowError('Grid layot is not set! Call setGeometry first.')
|
||||||
|
|
||||||
|
def getColumns(self):
|
||||||
|
"""
|
||||||
|
Get grid columns count.
|
||||||
|
Raises AddonWindowError if a grid has not yet been set.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.columns
|
||||||
|
except AttributeError:
|
||||||
|
raise AddonWindowError('Grid layout is not set! Call setGeometry first.')
|
||||||
|
|
||||||
|
def connect(self, event, function):
|
||||||
|
"""
|
||||||
|
Connect an event to a function.
|
||||||
|
|
||||||
|
An event can be an inctance of a Control object or an integer key action code.
|
||||||
|
Several basic key action codes are provided by PyXBMCT. More action codes can be found at
|
||||||
|
https://github.com/xbmc/xbmc/blob/master/xbmc/guilib/Key.h
|
||||||
|
|
||||||
|
You can connect the following Controls: Button, RadioButton and List. Other Controls do not
|
||||||
|
generate any control events when activated so their connections won't work.
|
||||||
|
To catch Slider events you need to connect the following key actions:
|
||||||
|
ACTION_MOVE_LEFT, ACTION_MOVE_RIGHT and ACTION_MOUSE_DRAG, and do a check
|
||||||
|
whether the Slider is focused.
|
||||||
|
|
||||||
|
"function" parameter is a function or a method to be executed. Note that you must provide
|
||||||
|
a function object [without brackets ()], not a function call!
|
||||||
|
lambda can be used as a function to call another function or method with parameters.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
self.connect(self.exit_button, self.close)
|
||||||
|
or
|
||||||
|
self.connect(ACTION_NAV_BACK, self.close)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.disconnect(event)
|
||||||
|
except AddonWindowError:
|
||||||
|
if type(event) == int:
|
||||||
|
self.actions_connected.append([event, function])
|
||||||
|
else:
|
||||||
|
self.controls_connected.append([event, function])
|
||||||
|
|
||||||
|
def connectEventList(self, events, function):
|
||||||
|
"""
|
||||||
|
Connect a list of controls/action codes to a function.
|
||||||
|
See connect docstring for more info.
|
||||||
|
"""
|
||||||
|
[self.connect(event, function) for event in events]
|
||||||
|
|
||||||
|
def disconnect(self, event):
|
||||||
|
"""
|
||||||
|
Disconnect an event from a function.
|
||||||
|
|
||||||
|
An event can be an inctance of a Control object or an integer key action code
|
||||||
|
which has previously been connected to a function or a method.
|
||||||
|
Raises AddonWindowError if an event is not connected to any function.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
self.disconnect(self.exit_button)
|
||||||
|
or
|
||||||
|
self.disconnect(ACTION_NAV_BACK)
|
||||||
|
"""
|
||||||
|
if type(event) == int:
|
||||||
|
event_list = self.actions_connected
|
||||||
|
else:
|
||||||
|
event_list = self.controls_connected
|
||||||
|
for index in range(len(event_list)):
|
||||||
|
if event == event_list[index][0]:
|
||||||
|
event_list.pop(index)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise AddonWindowError('The action or control %s is not connected!' % event)
|
||||||
|
|
||||||
|
def disconnectEventList(self, events):
|
||||||
|
"""
|
||||||
|
Disconnect a list of controls/action codes from functions.
|
||||||
|
See disconnect docstring for more info.
|
||||||
|
Raises AddonWindowError if at least one event in the list
|
||||||
|
is not connected to any function.
|
||||||
|
"""
|
||||||
|
[self.disconnect(event) for event in events]
|
||||||
|
|
||||||
|
def executeConnected(self, event, connected_list):
|
||||||
|
"""
|
||||||
|
Execute a connected event (an action or a control).
|
||||||
|
This is a helper method not to be called directly.
|
||||||
|
"""
|
||||||
|
for item in connected_list:
|
||||||
|
if event == item[0]:
|
||||||
|
item[1]()
|
||||||
|
break
|
||||||
|
|
||||||
|
def setAnimation(self, control):
|
||||||
|
"""
|
||||||
|
This method is called to set animation properties for all controls
|
||||||
|
added to the current addon window instance - both built-in controls
|
||||||
|
(window background, title bar etc.) and controls added with placeControl().
|
||||||
|
It receives a control instance as the 2nd positional argument (besides self).
|
||||||
|
By default the method does nothing, i.e. no animation is set for controls.
|
||||||
|
To add animation you need to re-implement this menthod in your child class.
|
||||||
|
|
||||||
|
E.g:
|
||||||
|
def setAnimation(self, control):
|
||||||
|
control.setAnimations([('WindowOpen', 'effect=fade start=0 end=100 time=1000',),
|
||||||
|
('WindowClose', 'effect=fade start=100 end=0 time=1000',)])
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _AddonWindow(_AbstractWindow):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Top-level control window.
|
||||||
|
|
||||||
|
The control windows serves as a parent widget for other XBMC UI controls
|
||||||
|
much like Tkinter.Tk or PyQt QWidget class.
|
||||||
|
This is an abstract class which is not supposed to be instantiated directly
|
||||||
|
and will raise exeptions. It is designed to be implemented in a grand-child class
|
||||||
|
with the second inheritance from xbmcgui.Window or xbmcgui.WindowDialog
|
||||||
|
in a direct child class.
|
||||||
|
|
||||||
|
This class provides a control window with a background and a header
|
||||||
|
similar to top-level widgets of desktop UI frameworks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, title=''):
|
||||||
|
"""Constructor method."""
|
||||||
|
super(_AddonWindow, self).__init__()
|
||||||
|
self.setFrame(title)
|
||||||
|
|
||||||
|
def setFrame(self, title):
|
||||||
|
"""
|
||||||
|
Define paths to images for window background and title background textures,
|
||||||
|
and set control position adjustment constants used in setGrid.
|
||||||
|
This is a helper method not to be called directly.
|
||||||
|
"""
|
||||||
|
# Window background image
|
||||||
|
self.background_img = os.path.join(_images, 'AddonWindow', 'ContentPanel.png')
|
||||||
|
# Background for a window header
|
||||||
|
self.title_background_img = os.path.join(_images, 'AddonWindow', 'dialogheader.png')
|
||||||
|
# Horisontal adjustment for a header background if the main background has transparent edges.
|
||||||
|
self.X_MARGIN = 5
|
||||||
|
# Vertical adjustment for a header background if the main background has transparent edges
|
||||||
|
self.Y_MARGIN = 5
|
||||||
|
# Header position adjustment if the main backround has visible borders.
|
||||||
|
self.Y_SHIFT = 4
|
||||||
|
# The height of a window header (for the title background and the title label).
|
||||||
|
self.HEADER_HEIGHT = 35
|
||||||
|
self.background = xbmcgui.ControlImage(-10, -10, 1, 1, self.background_img)
|
||||||
|
self.addControl(self.background)
|
||||||
|
self.setAnimation(self.background)
|
||||||
|
self.title_background = xbmcgui.ControlImage(-10, -10, 1, 1, self.title_background_img)
|
||||||
|
self.addControl(self.title_background)
|
||||||
|
self.setAnimation(self.title_background)
|
||||||
|
self.title_bar = xbmcgui.ControlLabel(-10, -10, 1, 1, title, alignment=ALIGN_CENTER, textColor='0xFFFFA500',
|
||||||
|
font='font13_title')
|
||||||
|
self.addControl(self.title_bar)
|
||||||
|
self.setAnimation(self.title_bar)
|
||||||
|
self.window_close_button = xbmcgui.ControlButton(-100, -100, 60, 30, '',
|
||||||
|
focusTexture=os.path.join(_images, 'AddonWindow', 'DialogCloseButton-focus.png'),
|
||||||
|
noFocusTexture=os.path.join(_images, 'AddonWindow', 'DialogCloseButton.png'))
|
||||||
|
self.addControl(self.window_close_button)
|
||||||
|
self.setAnimation(self.window_close_button)
|
||||||
|
|
||||||
|
def setGeometry(self, width_, height_, rows_, columns_, pos_x=-1, pos_y=-1, padding=5):
|
||||||
|
"""
|
||||||
|
Set width, height, Grid layout, and coordinates (optional) for a new control window.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
width_, height_: widgh and height of the created window.
|
||||||
|
rows_, columns_: rows and colums of the Grid layout to place controls on.
|
||||||
|
pos_x, pos_y (optional): coordinates of the top left corner of the window.
|
||||||
|
If pos_x and pos_y are not privided, the window will be placed
|
||||||
|
at the center of the screen.
|
||||||
|
padding (optional): padding between outer edges of the window and
|
||||||
|
controls placed on it.
|
||||||
|
Example:
|
||||||
|
self.setGeometry(400, 500, 5, 4)
|
||||||
|
"""
|
||||||
|
self.win_padding = padding
|
||||||
|
super(_AddonWindow, self).setGeometry(width_, height_, rows_, columns_, pos_x, pos_y)
|
||||||
|
self.background.setPosition(self.x, self.y)
|
||||||
|
self.background.setWidth(self.width)
|
||||||
|
self.background.setHeight(self.height)
|
||||||
|
self.title_background.setPosition(self.x + self.X_MARGIN, self.y + self.Y_MARGIN + self.Y_SHIFT)
|
||||||
|
self.title_background.setWidth(self.width - 2 * self.X_MARGIN)
|
||||||
|
self.title_background.setHeight(self.HEADER_HEIGHT)
|
||||||
|
self.title_bar.setPosition(self.x + self.X_MARGIN, self.y + self.Y_MARGIN + self.Y_SHIFT)
|
||||||
|
self.title_bar.setWidth(self.width - 2 * self.X_MARGIN)
|
||||||
|
self.title_bar.setHeight(self.HEADER_HEIGHT)
|
||||||
|
self.window_close_button.setPosition(self.x + self.width - 70, self.y + self.Y_MARGIN + self.Y_SHIFT)
|
||||||
|
|
||||||
|
def setGrid(self):
|
||||||
|
"""
|
||||||
|
Set window grid layout of rows * columns.
|
||||||
|
This is a helper method not to be called directly.
|
||||||
|
"""
|
||||||
|
self.grid_x = self.x + self.X_MARGIN + self.win_padding
|
||||||
|
self.grid_y = self.y + self.Y_MARGIN + self.Y_SHIFT + self.HEADER_HEIGHT + self.win_padding
|
||||||
|
self.tile_width = (self.width - 2 * (self.X_MARGIN + self.win_padding))/self.columns
|
||||||
|
self.tile_height = (
|
||||||
|
self.height - self.HEADER_HEIGHT - self.Y_SHIFT - 2 * (self.Y_MARGIN + self.win_padding))/self.rows
|
||||||
|
|
||||||
|
def setWindowTitle(self, title=''):
|
||||||
|
"""
|
||||||
|
Set window title.
|
||||||
|
This method must be called AFTER (!!!) setGeometry(),
|
||||||
|
otherwise there is some werid bug with all skin text labels set to the 'title' text.
|
||||||
|
Example:
|
||||||
|
self.setWindowTitle('My Cool Addon')
|
||||||
|
"""
|
||||||
|
self.title_bar.setLabel(title)
|
||||||
|
|
||||||
|
def getWindowTitle(self):
|
||||||
|
"""Get window title."""
|
||||||
|
return self.title_bar.getLabel()
|
||||||
|
|
||||||
|
class _FullWindow(xbmcgui.Window):
|
||||||
|
|
||||||
|
"""An abstract class to define window event processing."""
|
||||||
|
|
||||||
|
def onAction(self, action):
|
||||||
|
"""
|
||||||
|
Catch button actions.
|
||||||
|
Note that, despite being compared to an integer,
|
||||||
|
action is an instance of xbmcgui.Action class.
|
||||||
|
"""
|
||||||
|
if action == ACTION_PREVIOUS_MENU:
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
self.executeConnected(action, self.actions_connected)
|
||||||
|
|
||||||
|
def onControl(self, control):
|
||||||
|
"""
|
||||||
|
Catch activated controls.
|
||||||
|
Control is an instance of xbmcgui.Control class.
|
||||||
|
"""
|
||||||
|
if control == self.window_close_button:
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
self.executeConnected(control, self.controls_connected)
|
||||||
|
|
||||||
|
|
||||||
|
class _DialogWindow(xbmcgui.WindowDialog):
|
||||||
|
|
||||||
|
"""An abstract class to define window event processing."""
|
||||||
|
|
||||||
|
def onAction(self, action):
|
||||||
|
"""
|
||||||
|
Catch button actions.
|
||||||
|
Note that, despite being compared to an integer,
|
||||||
|
action is an instance of xbmcgui.Action class.
|
||||||
|
"""
|
||||||
|
if action == ACTION_PREVIOUS_MENU:
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
self.executeConnected(action, self.actions_connected)
|
||||||
|
|
||||||
|
def onControl(self, control):
|
||||||
|
"""
|
||||||
|
Catch activated controls.
|
||||||
|
Control is an instance of xbmcgui.Control class.
|
||||||
|
"""
|
||||||
|
if control == self.window_close_button:
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
self.executeConnected(control, self.controls_connected)
|
||||||
|
|
||||||
|
|
||||||
|
class BlankFullWindow(_FullWindow, _AbstractWindow):
|
||||||
|
"""
|
||||||
|
Addon UI container with a solid background.
|
||||||
|
This is a blank window with a black background and without any elements whatsoever.
|
||||||
|
The decoration and layout are completely up to an addon developer.
|
||||||
|
The window controls can hide under video or music visualization.
|
||||||
|
Window ID can be passed on class instantiation an agrument
|
||||||
|
but __init__ must have the 2nd fake argument, e.g:
|
||||||
|
|
||||||
|
def __init__(self, *args)
|
||||||
|
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
addon = MyAddon('My Cool Addon')
|
||||||
|
addon.setGeometry(400, 300, 4, 3)
|
||||||
|
addon.doModal()
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BlankDialogWindow(_DialogWindow, _AbstractWindow):
|
||||||
|
"""
|
||||||
|
Addon UI container with a transparent background.
|
||||||
|
This is a blank window with a transparent background and without any elements whatsoever.
|
||||||
|
The decoration and layout are completely up to an addon developer.
|
||||||
|
The window controls are always displayed over video or music visualization.
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
addon = MyAddon('My Cool Addon')
|
||||||
|
addon.setGeometry(400, 300, 4, 3)
|
||||||
|
addon.doModal()
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AddonFullWindow(_FullWindow, _AddonWindow):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Addon UI container with a solid background.
|
||||||
|
Control window is displayed on top of the main background image - self.main_bg.
|
||||||
|
Video and music visualization are displayed unhindered.
|
||||||
|
Window ID can be passed on class instantiation as the 2nd positional agrument
|
||||||
|
but __init__ must have the 3rd fake argument, e.g:
|
||||||
|
|
||||||
|
def __init__(self, title='', *args)
|
||||||
|
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
addon = MyAddon('My Cool Addon')
|
||||||
|
addon.setGeometry(400, 300, 4, 3)
|
||||||
|
addon.doModal()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __new__(cls, title='', *args, **kwargs):
|
||||||
|
return super(AddonFullWindow, cls).__new__(cls, *args, **kwargs)
|
||||||
|
|
||||||
|
def setFrame(self, title):
|
||||||
|
"""
|
||||||
|
Set the image for for the fullscreen background.
|
||||||
|
"""
|
||||||
|
# Image for the fullscreen background.
|
||||||
|
self.main_bg_img = os.path.join(_images, 'AddonWindow', 'SKINDEFAULT.jpg')
|
||||||
|
# Fullscreen background image control.
|
||||||
|
self.main_bg = xbmcgui.ControlImage(1, 1, 1280, 720, self.main_bg_img)
|
||||||
|
self.addControl(self.main_bg)
|
||||||
|
super(AddonFullWindow, self).setFrame(title)
|
||||||
|
|
||||||
|
def setBackground(self, image=''):
|
||||||
|
"""
|
||||||
|
Set the main bacground to an image file.
|
||||||
|
image: path to an image file as str.
|
||||||
|
Example:
|
||||||
|
self.setBackground('d:\images\bacground.png')
|
||||||
|
"""
|
||||||
|
self.main_bg.setImage(image)
|
||||||
|
|
||||||
|
|
||||||
|
class AddonDialogWindow(_DialogWindow, _AddonWindow):
|
||||||
|
"""
|
||||||
|
Addon UI container with a transparent background.
|
||||||
|
Control window is displayed on top of XBMC UI,
|
||||||
|
including video an music visualization!
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
addon = MyAddon('My Cool Addon')
|
||||||
|
addon.setGeometry(400, 300, 4, 3)
|
||||||
|
addon.doModal()
|
||||||
|
"""
|
||||||
|
pass
|
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1,164 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import pickle
|
||||||
|
import threading
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcvfs
|
||||||
|
import xbmcgui
|
||||||
|
import Localization
|
||||||
|
from net import HTTP
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from sqlite3 import dbapi2 as sqlite
|
||||||
|
except:
|
||||||
|
from pysqlite2 import dbapi2 as sqlite
|
||||||
|
|
||||||
|
rtrCache_lock = threading.RLock()
|
||||||
|
|
||||||
|
|
||||||
|
class Cache:
|
||||||
|
def __init__(self, name, version, expire=0, size=0, step=100):
|
||||||
|
self.name = name
|
||||||
|
self.version = version
|
||||||
|
self._connect()
|
||||||
|
if expire:
|
||||||
|
self.expire(expire)
|
||||||
|
if size:
|
||||||
|
self.size(size, step)
|
||||||
|
|
||||||
|
def get(self, token, callback, *param):
|
||||||
|
cur = self.db.cursor()
|
||||||
|
cur.execute('select expire,data from cache where id=? limit 1', (token, ))
|
||||||
|
row = cur.fetchone()
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
if row[0] and row[0] < int(time.time()):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
obj = pickle.loads(row[1])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
response = callback(*param)
|
||||||
|
|
||||||
|
if response[0]:
|
||||||
|
obj = sqlite.Binary(pickle.dumps(response[1]))
|
||||||
|
curtime = int(time.time())
|
||||||
|
cur = self.db.cursor()
|
||||||
|
if isinstance(response[0], bool):
|
||||||
|
cur.execute('replace into cache(id,addtime,expire,data) values(?,?,?,?)', (token, curtime, None, obj))
|
||||||
|
else:
|
||||||
|
cur.execute('replace into cache(id,addtime,expire,data) values(?,?,?,?)',
|
||||||
|
(token, curtime, curtime + response[0], obj))
|
||||||
|
self.db.commit()
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
return response[1]
|
||||||
|
|
||||||
|
def expire(self, expire):
|
||||||
|
# with rtrCache_lock:
|
||||||
|
cur = self.db.cursor()
|
||||||
|
cur.execute('delete from cache where addtime<?', (int(time.time()) - expire, ))
|
||||||
|
self.db.commit()
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
def size(self, size, step=100):
|
||||||
|
# with rtrCache_lock:
|
||||||
|
while True:
|
||||||
|
if os.path.getsize(self.filename) < size:
|
||||||
|
break
|
||||||
|
cur = self.db.cursor()
|
||||||
|
cur.execute('select id from cache order by addtime asc limit ?', (step, ))
|
||||||
|
rows = cur.fetchall()
|
||||||
|
if not rows:
|
||||||
|
cur.close()
|
||||||
|
break
|
||||||
|
cur.execute('delete from cache where id in (' + ','.join(len(rows) * '?') + ')', [x[0] for x in rows])
|
||||||
|
self.db.commit()
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
# with rtrCache_lock:
|
||||||
|
cur = self.db.cursor()
|
||||||
|
cur.execute('delete from cache')
|
||||||
|
self.db.commit()
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
with rtrCache_lock:
|
||||||
|
dirname = xbmc.translatePath('special://temp')
|
||||||
|
for subdir in ('xbmcup', 'plugin.video.torrenter'):
|
||||||
|
dirname = os.path.join(dirname, subdir)
|
||||||
|
if not xbmcvfs.exists(dirname):
|
||||||
|
xbmcvfs.mkdir(dirname)
|
||||||
|
|
||||||
|
self.filename = os.path.join(dirname, self.name)
|
||||||
|
|
||||||
|
first = False
|
||||||
|
if not xbmcvfs.exists(self.filename):
|
||||||
|
first = True
|
||||||
|
|
||||||
|
self.db = sqlite.connect(self.filename, check_same_thread=False)
|
||||||
|
if not first:
|
||||||
|
cur = self.db.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute('select version from db_ver')
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row or float(row[0]) != self.version:
|
||||||
|
cur.execute('drop table cache')
|
||||||
|
cur.execute('drop table if exists db_ver')
|
||||||
|
first = True
|
||||||
|
except:
|
||||||
|
cur.execute('drop table cache')
|
||||||
|
first = True
|
||||||
|
self.db.commit()
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
if first and not self.first_time():
|
||||||
|
cur = self.db.cursor()
|
||||||
|
cur.execute('pragma auto_vacuum=1')
|
||||||
|
cur.execute('create table cache(id varchar(255) unique, addtime integer, expire integer, data blob)')
|
||||||
|
cur.execute('create index time on cache(addtime asc)')
|
||||||
|
cur.execute('create table db_ver(version real)')
|
||||||
|
cur.execute('insert into db_ver(version) values(?)', (self.version, ))
|
||||||
|
self.db.commit()
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
def first_time(self):
|
||||||
|
scrapers = {'tvdb': 'TheTVDB.com', 'tmdb': 'TheMovieDB.org', 'kinopoisk': 'KinoPoisk.ru'}
|
||||||
|
ok = xbmcgui.Dialog().yesno(Localization.localize('Content Lists'),
|
||||||
|
Localization.localize('Do you want to preload full metadata?') + ' (%s)' % (
|
||||||
|
scrapers[os.path.basename(self.filename).split('.')[0]]),
|
||||||
|
Localization.localize('It is highly recommended!'))
|
||||||
|
if ok:
|
||||||
|
return self.download()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def download(self):
|
||||||
|
dirname = os.path.dirname(self.filename)
|
||||||
|
print self.filename
|
||||||
|
zipname = os.path.basename(self.filename).rstrip('.db') + '.zip'
|
||||||
|
url = 'http://www.tat-store.ru/torrenter/' + zipname
|
||||||
|
self.http = HTTP()
|
||||||
|
response = self.http.fetch(url, download=os.path.join(dirname, zipname), progress=True)
|
||||||
|
if response.error:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
filezip = zipfile.ZipFile(os.path.join(dirname, zipname), 'r')
|
||||||
|
filezip.extractall(dirname)
|
||||||
|
filezip.close()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# encoding: utf-8
|
||||||
|
"""
|
||||||
|
StringMatcher.py
|
||||||
|
|
||||||
|
ported from python-Levenshtein
|
||||||
|
[https://github.com/miohtama/python-Levenshtein]
|
||||||
|
"""
|
||||||
|
|
||||||
|
from Levenshtein import *
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
class StringMatcher:
|
||||||
|
"""A SequenceMatcher-like class built on the top of Levenshtein"""
|
||||||
|
|
||||||
|
def _reset_cache(self):
|
||||||
|
self._ratio = self._distance = None
|
||||||
|
self._opcodes = self._editops = self._matching_blocks = None
|
||||||
|
|
||||||
|
def __init__(self, isjunk=None, seq1='', seq2=''):
|
||||||
|
if isjunk:
|
||||||
|
warn("isjunk not NOT implemented, it will be ignored")
|
||||||
|
self._str1, self._str2 = seq1, seq2
|
||||||
|
self._reset_cache()
|
||||||
|
|
||||||
|
def set_seqs(self, seq1, seq2):
|
||||||
|
self._str1, self._str2 = seq1, seq2
|
||||||
|
self._reset_cache()
|
||||||
|
|
||||||
|
def set_seq1(self, seq1):
|
||||||
|
self._str1 = seq1
|
||||||
|
self._reset_cache()
|
||||||
|
|
||||||
|
def set_seq2(self, seq2):
|
||||||
|
self._str2 = seq2
|
||||||
|
self._reset_cache()
|
||||||
|
|
||||||
|
def get_opcodes(self):
|
||||||
|
if not self._opcodes:
|
||||||
|
if self._editops:
|
||||||
|
self._opcodes = opcodes(self._editops, self._str1, self._str2)
|
||||||
|
else:
|
||||||
|
self._opcodes = opcodes(self._str1, self._str2)
|
||||||
|
return self._opcodes
|
||||||
|
|
||||||
|
def get_editops(self):
|
||||||
|
if not self._editops:
|
||||||
|
if self._opcodes:
|
||||||
|
self._editops = editops(self._opcodes, self._str1, self._str2)
|
||||||
|
else:
|
||||||
|
self._editops = editops(self._str1, self._str2)
|
||||||
|
return self._editops
|
||||||
|
|
||||||
|
def get_matching_blocks(self):
|
||||||
|
if not self._matching_blocks:
|
||||||
|
self._matching_blocks = matching_blocks(self.get_opcodes(),
|
||||||
|
self._str1, self._str2)
|
||||||
|
return self._matching_blocks
|
||||||
|
|
||||||
|
def ratio(self):
|
||||||
|
if not self._ratio:
|
||||||
|
self._ratio = ratio(self._str1, self._str2)
|
||||||
|
return self._ratio
|
||||||
|
|
||||||
|
def quick_ratio(self):
|
||||||
|
# This is usually quick enough :o)
|
||||||
|
if not self._ratio:
|
||||||
|
self._ratio = ratio(self._str1, self._str2)
|
||||||
|
return self._ratio
|
||||||
|
|
||||||
|
def real_quick_ratio(self):
|
||||||
|
len1, len2 = len(self._str1), len(self._str2)
|
||||||
|
return 2.0 * min(len1, len2) / (len1 + len2)
|
||||||
|
|
||||||
|
def distance(self):
|
||||||
|
if not self._distance:
|
||||||
|
self._distance = distance(self._str1, self._str2)
|
||||||
|
return self._distance
|