├── .codeclimate.yml ├── .gitignore ├── Other ├── KODI Popcorn Time - Graphic.psd ├── Vector Graphics │ └── KODI Popcorn Time - logo.svg └── fanart.jpg ├── README.md ├── Releases └── plugin.video.kodipopcorntime.repository │ ├── icon.png │ ├── plugin.video.kodipopcorntime.repository-1.0.0.zip │ └── plugin.video.kodipopcorntime.repository-1.1.0.zip ├── Screenshots └── Version-1.5.x │ ├── Anime Genres.png │ ├── Anime.png │ ├── Categories.png │ ├── KODI Popcorn Time.png │ ├── Loading.png │ ├── Movies and TV Shows Genres.png │ ├── Movies.png │ └── TV Shows.png ├── addons.xml ├── addons.xml.md5 └── plugin.video.kodipopcorntime ├── LICENSE ├── README ├── addon.py ├── addon.xml ├── changelog.txt ├── fanart.jpg ├── icon.png └── resources ├── bin ├── android_arm │ └── torrent2http ├── darwin_x64 │ ├── libboost_system-mt.dylib │ └── torrent2http ├── linux_arm │ └── torrent2http ├── linux_x64 │ └── torrent2http ├── linux_x86 │ └── torrent2http ├── windows_x64 │ └── torrent2http.exe └── windows_x86 │ └── torrent2http.exe ├── language ├── Croatian │ └── strings.xml ├── Dutch │ └── strings.xml ├── English │ └── strings.xml ├── Hebrew │ └── strings.xml ├── Polish │ └── strings.xml ├── Portuguese │ └── strings.xml ├── Slovenian │ └── strings.xml └── Spanish │ └── strings.xml ├── lib ├── __init__.py ├── kodipopcorntime │ ├── __init__.py │ ├── exceptions.py │ ├── favourites.py │ ├── gui │ │ ├── __init__.py │ │ ├── base.py │ │ ├── base2.py │ │ ├── base3.py │ │ ├── browse.py │ │ ├── cmd.py │ │ ├── folders.py │ │ ├── index.py │ │ ├── player.py │ │ └── search.py │ ├── logging.py │ ├── media.py │ ├── platform.py │ ├── providers │ │ ├── __init__.py │ │ ├── api-fetch.py │ │ ├── apifetch │ │ │ ├── __init__.py │ │ │ ├── anime.py │ │ │ ├── base.py │ │ │ ├── movies.py │ │ │ └── tvShows.py │ │ └── movies │ │ │ ├── __init__.py │ │ │ ├── metadata_tmdb.py │ │ │ └── subtitle_yify.py │ ├── request.py │ ├── settings │ │ ├── __init__.py │ │ ├── addon.py │ │ ├── base.py │ │ ├── base2.py │ │ └── movies.py │ ├── threads.py │ ├── torrent.py │ └── utils.py └── simplejson │ ├── __init__.py │ ├── compat.py │ ├── decoder.py │ ├── encoder.py │ ├── ordered_dict.py │ ├── scanner.py │ └── tool.py ├── media ├── background │ ├── halloween.jpg │ ├── new_year.jpg │ ├── valentine.jpg │ └── xmass.jpg ├── black.png ├── categories │ ├── Anime.png │ ├── Movies.png │ └── TVShows.png ├── error.png ├── info.png ├── movies │ ├── back.png │ ├── back_thumbnail.png │ ├── genres.png │ ├── genres │ │ ├── Action.png │ │ ├── Adventure.png │ │ ├── Animation.png │ │ ├── Biography.png │ │ ├── Comedy.png │ │ ├── Crime.png │ │ ├── Documentary.png │ │ ├── Drama.png │ │ ├── Family.png │ │ ├── Fantasy.png │ │ ├── Film-Noir.png │ │ ├── History.png │ │ ├── Horror.png │ │ ├── Music.png │ │ ├── Musical.png │ │ ├── Mystery.png │ │ ├── Romance.png │ │ ├── Sci-Fi.png │ │ ├── Sport.png │ │ ├── Thriller.png │ │ ├── War.png │ │ └── Western.png │ ├── more.png │ ├── more_thumbnail.png │ ├── popular.png │ ├── rated.png │ ├── recently.png │ └── search.png ├── tvshows │ ├── back.png │ ├── back_thumbnail.png │ ├── genres.png │ ├── genres │ │ ├── Action.png │ │ ├── Adventure.png │ │ ├── Animation.png │ │ ├── Biography.png │ │ ├── Comedy.png │ │ ├── Crime.png │ │ ├── Documentary.png │ │ ├── Drama.png │ │ ├── Family.png │ │ ├── Fantasy.png │ │ ├── Film-Noir.png │ │ ├── History.png │ │ ├── Horror.png │ │ ├── Music.png │ │ ├── Musical.png │ │ ├── Mystery.png │ │ ├── Romance.png │ │ ├── Sci-Fi.png │ │ ├── Sport.png │ │ ├── Thriller.png │ │ ├── War.png │ │ └── Western.png │ ├── more.png │ ├── more_thumbnail.png │ ├── popular.png │ ├── rated.png │ ├── recently.png │ └── search.png └── warning.png └── settings.xml /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | Ruby: true 3 | JavaScript: true 4 | PHP: true 5 | Python: true 6 | exclude_paths: 7 | - "plugin.video.kodipopcorntime/resources/lib/simplejson/*" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # project 2 | .vs/ 3 | 4 | # general 5 | .DS_Store 6 | Thumbs.db 7 | ehthumbs.db 8 | .idea/ 9 | *.pyo 10 | -------------------------------------------------------------------------------- /Other/KODI Popcorn Time - Graphic.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Other/KODI Popcorn Time - Graphic.psd -------------------------------------------------------------------------------- /Other/Vector Graphics/KODI Popcorn Time - logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Other/fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Other/fanart.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Not Working on this project anymore # 2 | 3 | #This is the continuation of Diblo's work on bringing Popcorn Time to KODI.# 4 | 5 |
6 | 7 | # KODI Popcorn Time # 8 | 9 | [![Donate](https://img.shields.io/badge/Donate-Paypal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=83UA43TPBRHLL) 10 | 11 | ## What it is ## 12 | With KODI Popcorn Time you can search for Movies, TV Shows and Animes that you can see immediately in KODI. 13 | 14 | I do this as a hobby and release it for free. But if you like it and want to you can always buy me a beer using the PayPal donation button on my [Developer Github Page](https://markop159.github.io), or donate via Bitcoin QR code on bottom of the page. 15 | 16 | ## Download ## 17 | Best way to install KODI Popcorn Time is to install [Repository](https://github.com/markop159/Markop159-repository/blob/master/Releases/plugin.video.kodipopcorntime.repository/plugin.video.kodipopcorntime.repository-1.1.0.zip?raw=true) on your KODI and install it from there so it will update automaticaly when new version becomes available. 18 | 19 | ## Supported Platforms ## 20 | * XBMC/KODI 13.x and later 21 | * Windows x32 and x64 22 | * OS X x32 and x64 23 | * Linux x32, x64 and ARM 24 | * Raspberry Pi 25 | * Android ARM 4.0+ 26 | 27 | ## Issues ## 28 | Please, file an issue :) - [Issues](https://github.com/markop159/KODI-Popcorn-Time/issues), or go to [TVADDONS forum](https://forums.tvaddons.ag/addon-releases/47568-kodi-popcorn-time.html) and post there. 29 | 30 | ## torrent2http not found Issue ## 31 | Please refer to [Issue 102](https://github.com/markop159/KODI-Popcorn-Time/issues/102), and follow this instructions -> 32 | 33 | On directory: 34 | *"C:\Users\Admin\AppData\Roaming\Kodi\addons\plugin.video.kodipopcorntime\resources\bin"* 35 | 36 | Rename folder "windows_x86" to "windows_x64" and restart Kodi. 37 | 38 | ## TODO ## 39 | - [x] Resolve timeout Issue 40 | - [x] TV shows section 41 | - [x] Next page for TV shows and Anime 42 | - [x] Metadata for TV-Shows 43 | - [x] Add 480p selection 44 | - [x] Clean-up settings page 45 | - [x] Clean-up code 46 | - [x] Clean-up media folder 47 | - [x] Metadata for Anime 48 | - [ ] Torrent health (seeds) 49 | 50 | ## Credits ## 51 | * Fixed KODI Popcorn Time add-on from [Diblo](https://github.com/Diblo/KODI-Popcorn-Time) 52 | * KODI Popcorn Time is a rewrite of [xbmctorrent](http://github.com/steeve/xbmctorrent). 53 | * [libtorrent-go](http://github.com/steeve/libtorrent-go) 54 | * [torrent2http](http://github.com/steeve/torrent2http) 55 | 56 | ## Changelog ## 57 | 58 | #### KODI Popcorn Time Addon #### 59 | 60 | * Ver 1.6.7 61 | - Added favourites for TV Shows and Anime (tnx @adamantike for fixes) 62 | - Favourites are stil WIP (needs to implement clearing favourites from settings) 63 | - translation fixes by @adamantike and @muzena 64 | 65 | * #### [Full Changelog](https://github.com/markop159/KODI-Popcorn-Time/wiki/Changelog) #### 66 | 67 | #### Markop159 Repository #### 68 | 69 | - [Has been moved to new location](https://github.com/markop159/Markop159-repository) 70 | 71 | ## Need more information ## 72 | Check out our [WIKI page](https://github.com/markop159/KODI-Popcorn-Time/wiki) 73 | 74 | ## Donate ## 75 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=83UA43TPBRHLL) 76 | 77 | ![1LF9LFXyr31ZWNyjBye53LHee6QhL8U1cW](http://i.imgur.com/501JXzC.png) 78 | -------------------------------------------------------------------------------- /Releases/plugin.video.kodipopcorntime.repository/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Releases/plugin.video.kodipopcorntime.repository/icon.png -------------------------------------------------------------------------------- /Releases/plugin.video.kodipopcorntime.repository/plugin.video.kodipopcorntime.repository-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Releases/plugin.video.kodipopcorntime.repository/plugin.video.kodipopcorntime.repository-1.0.0.zip -------------------------------------------------------------------------------- /Releases/plugin.video.kodipopcorntime.repository/plugin.video.kodipopcorntime.repository-1.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Releases/plugin.video.kodipopcorntime.repository/plugin.video.kodipopcorntime.repository-1.1.0.zip -------------------------------------------------------------------------------- /Screenshots/Version-1.5.x/Anime Genres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Screenshots/Version-1.5.x/Anime Genres.png -------------------------------------------------------------------------------- /Screenshots/Version-1.5.x/Anime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Screenshots/Version-1.5.x/Anime.png -------------------------------------------------------------------------------- /Screenshots/Version-1.5.x/Categories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Screenshots/Version-1.5.x/Categories.png -------------------------------------------------------------------------------- /Screenshots/Version-1.5.x/KODI Popcorn Time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Screenshots/Version-1.5.x/KODI Popcorn Time.png -------------------------------------------------------------------------------- /Screenshots/Version-1.5.x/Loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Screenshots/Version-1.5.x/Loading.png -------------------------------------------------------------------------------- /Screenshots/Version-1.5.x/Movies and TV Shows Genres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Screenshots/Version-1.5.x/Movies and TV Shows Genres.png -------------------------------------------------------------------------------- /Screenshots/Version-1.5.x/Movies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Screenshots/Version-1.5.x/Movies.png -------------------------------------------------------------------------------- /Screenshots/Version-1.5.x/TV Shows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/Screenshots/Version-1.5.x/TV Shows.png -------------------------------------------------------------------------------- /addons.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | video 12 | 13 | 14 | all 15 | 16 | 17 | English 18 | 19 | 20 | false 21 | false 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | video 33 | 34 | 35 | all 36 | 37 | 38 | English 39 | 40 | 41 | false 42 | false 43 | 44 | 45 | 49 | 50 | https://raw.githubusercontent.com/markop159/Markop159-repository/master/addons.xml 51 | https://raw.githubusercontent.com/markop159/Markop159-repository/master/addons.xml.md5 52 | https://raw.githubusercontent.com/markop159/Markop159-repository/master/Releases 53 | 54 | 55 | all 56 | 57 | 58 | English 59 | 60 | 61 | false 62 | false 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /addons.xml.md5: -------------------------------------------------------------------------------- 1 | c21a42efd2e5aa03ffe19d6cd50635ff 2 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/README: -------------------------------------------------------------------------------- 1 | KODI Popcorn Time 2 | ================= 3 | 4 | What it is 5 | ---------- 6 | With KODI Popcorn Time you can search for movies that you can see immediately in KODI. 7 | 8 | Download 9 | -------- 10 | Check out https://github.com/markop159/KODI-Popcorn-Time/tree/master/Releases to download the ZIP file. 11 | 12 | Supported Platforms 13 | ------------------- 14 | * XBMC/KODI 13.x and later 15 | * Windows x32 and x64 16 | * OSX x32 and x64 17 | * Linux x32, x64 and ARM 18 | * Android ARM 4.0+ 19 | * Darwin x64 20 | * Raspberry Pi 21 | 22 | Discussions/Proposals 23 | --------------------- 24 | Proposals may be posted under https://github.com/markop159/KODI-Popcorn-Time/issues. 25 | 26 | Issues 27 | ------ 28 | Please, file an issue :) - https://github.com/markop159/KODI-Popcorn-Time/issues 29 | 30 | Credits 31 | ------- 32 | * Fixed KODI Popcorn Time add-on from https://github.com/Diblo/KODI-Popcorn-Time 33 | * KODI Popcorn Time is a rewrite of http://github.com/steeve/xbmctorrent. 34 | * http://github.com/steeve/libtorrent-go 35 | * http://github.com/steeve/torrent2http 36 | 37 | Changelog 38 | --------- 39 | Check out first README on https://github.com/markop159/KODI-Popcorn-Time. 40 | 41 | Need more information 42 | --------------------- 43 | Check out our WIKI page https://github.com/markop159/KODI-Popcorn-Time/wiki! 44 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/addon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys, os, xbmcaddon, urlparse, urllib 3 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'resources', 'lib')) 4 | __addon__ = xbmcaddon.Addon() 5 | from kodipopcorntime.platform import Platform 6 | from kodipopcorntime import gui 7 | from kodipopcorntime import settings 8 | from kodipopcorntime.exceptions import Notify, Error, HTTPError, ProxyError, TorrentError, Abort 9 | from kodipopcorntime.logging import log, LOGLEVEL, log_error 10 | from kodipopcorntime.utils import notify, NOTIFYLEVEL 11 | 12 | 13 | def _fix(params): 14 | if not params.get('endpoint'): 15 | params = settings.movies.provider.folders(None)[0]["params"] 16 | params['mediaType'] = 'movies' 17 | settings.addon.cur_uri = "%s?%s" %(settings.addon.base_url, urllib.urlencode(params)) 18 | return params 19 | 20 | if __name__ == '__main__': 21 | try: 22 | reload(sys) 23 | sys.setdefaultencoding("utf-8") 24 | 25 | log("(Main) Starting %s version %s build %s - Platform: %s %s" %(settings.addon.name, settings.addon.version, settings.BUILD, Platform.system, Platform.arch), LOGLEVEL.INFO) 26 | 27 | log("(Main) Platform: %s" %sys.platform) 28 | if hasattr(os, 'uname'): 29 | log("(Main) Uname: %s" %str(os.uname())) 30 | log("(Main) Environ: %s" %str(os.environ)) 31 | 32 | if not Platform.system: 33 | raise Error("Unsupported OS", 30302) 34 | 35 | params = dict(urlparse.parse_qsl(settings.addon.cur_uri)) 36 | cmd = params.get('cmd') 37 | 38 | if not cmd: 39 | params = _fix(params) 40 | getattr(gui, params.pop('endpoint', 'index'))(params.pop('mediaType', '')).show(**params) 41 | elif cmd in ('add_fav', 'remove_fav'): 42 | getattr(gui.cmd, cmd)(params.get('action'), params.get('id')) 43 | else: 44 | getattr(gui.cmd, cmd)() 45 | 46 | except (Error, HTTPError, ProxyError, TorrentError) as e: 47 | notify(e.messageID, level=NOTIFYLEVEL.ERROR) 48 | log_error() 49 | except Notify as e: 50 | notify(e.messageID, e.message, level=e.level) 51 | log("(Main) Notify: %s" %str(e), LOGLEVEL.NOTICE) 52 | sys.exc_clear() 53 | except Abort: 54 | log("(Main) Abort", LOGLEVEL.INFO) 55 | sys.exc_clear() 56 | except: 57 | notify(30308, level=NOTIFYLEVEL.ERROR) 58 | log_error() 59 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/addon.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | video 8 | 9 | 10 | false 11 | all 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/changelog.txt: -------------------------------------------------------------------------------- 1 | v1.7.6 2 | - Update API 3 | 4 | v1.7.5 5 | - fix anime list, favourites and add portuguese lang #124 thanks to @daniel3xxx 6 | 7 | v1.7.4 8 | - fix for Could not receive media list #123 for movies thanks to @daniel3xxx 9 | - fix for TV Shows 10 | 11 | v1.7.3 12 | - Fixed "cannot find torrent2http" error on x64 windows systems 13 | - Fixed "connection refused to torrent2http" error on latest revisions of linux 14 | 15 | v1.7.2 16 | - Fixes 17 | 18 | v1.7.1 19 | - Metadata for movies fix 20 | 21 | v1.7.0 22 | - Additional metadata information scraping for movies, TV Shows and Anime 23 | - Watch Later list for Movies implemented 24 | - Now you can remove all favourites for mediatype with context menu item 25 | - Last feature ritch update new updates will be more of bug fix updates 26 | 27 | v1.6.8 28 | - Multiple Changes and Fixes 29 | 30 | v1.6.7 31 | - Added favourites for TV Shows and Anime (tnx @adamantike for fixes) 32 | - Favourites are stil WIP (needs to implement clearing favourites from settings) 33 | - translation fixes by @adamantike and @muzena 34 | 35 | v1.6.6 36 | - Croatian translation by @muzena 37 | - Proper fix for issue #43 by @adamantike 38 | - Data fetching from api related code changes (movies, tvshows, anime in separated files) 39 | - Added more info for TV Shows and Anime 40 | - And more... 41 | 42 | v1.6.5 43 | - Fixed Issue #43 44 | 45 | v1.6.4 46 | - Slovenian translation 47 | - Additional fixes 48 | 49 | v1.6.3 50 | - Trailer in context menu @tatostao 51 | - Spanish translation updated by @adamantike 52 | - Additional fixes by @adamantike 53 | 54 | v1.6.2 55 | - Support for ARMv6 56 | 57 | v1.6.1 58 | - fixed trailer issue (not fetching movies) 59 | - updated Polish translation 60 | 61 | v1.6.0 62 | - Release version 63 | - Minor fixes 64 | - Code Cleanup 65 | - Fun stuff 66 | 67 | v1.5.5.rc3 68 | - Fixed anime bug 69 | - test adding armv6 support 70 | 71 | v1.5.4.rc2 72 | - Fixes 73 | - Polishing 74 | - Fun stuff 75 | - More icons 76 | 77 | v1.5.3.rc1 78 | - Fixed Issue #11 79 | - Added trailers for movies Issue #10 80 | - Added 480p selection 81 | 82 | v1.5.2.b 83 | - Metadata for TV-Shows 84 | - Fixed torrents with url=None 85 | 86 | v1.5.1.b 87 | - Show more for TV-Shows and Anime section 88 | - Cleaned-up settings page (partialy) 89 | - Cleaned-up code (partialy) 90 | - Fixed Issue #8 91 | - Fixed Issue #9 92 | 93 | v1.5.0.b 94 | - Added TV Shows section 95 | - Added Anime Section 96 | - Changed API 97 | 98 | v1.0.1.b Beta 2 99 | - Changed Provider 100 | - Fixed timeout Issue on Raspberry PI 101 | 102 | v1.0.0.b Beta 1 103 | - Movies are working again 104 | - Fixed Download is Complete Error 105 | 106 | v0.7.0 alpha 2 107 | - Checks whether there is room for movie 108 | - Improved view and navigation 109 | - More icons 110 | - Improved setting possibilities 111 | - Setting: Improved proxy settings 112 | - Search 113 | - Better cache cleanup 114 | - Several modules have been removed 115 | - New structure 116 | - Do not show the loading window, when there is a cache 117 | - Improved debugging 118 | 119 | v0.7.0 alpha 120 | - New and more icons 121 | - New and improved setting possibilities (You will love it!) 122 | - Has become ready to integrate TV series provider 123 | - Setting: Preferred video quality 124 | - Setting: Download and upload rate limit 125 | - Setting: Prioritize hearing impaired subtitles 126 | - Setting: Ability to add more proxy domains at once 127 | - Integrated proxy domains 128 | - New and better torrent2http provider (https://github.com/anteo/torrent2http) 129 | - Several modules have been removed 130 | - Tuning and optimization 131 | - New structure 132 | 133 | v0.6.5 134 | - Support XBMC/KODI 13.x and later 135 | 136 | v0.6.4 137 | - Support Linux ARM 138 | - Removed Android x86 support 139 | 140 | v0.6.3 141 | - Improving and optimize metadata 142 | - Improving and optimize subtitles 143 | - Improving clean cache 144 | - Improving loading bar 145 | - Improvements and restructuring 146 | - FIX: Android issue 147 | - FIX: "Add to movies" 148 | - FIX: Subtitle caching issue 149 | 150 | v0.6.0 151 | - Initial release 152 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/fanart.jpg -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/icon.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/bin/android_arm/torrent2http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/bin/android_arm/torrent2http -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/bin/darwin_x64/libboost_system-mt.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/bin/darwin_x64/libboost_system-mt.dylib -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/bin/darwin_x64/torrent2http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/bin/darwin_x64/torrent2http -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/bin/linux_arm/torrent2http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/bin/linux_arm/torrent2http -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/bin/linux_x64/torrent2http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/bin/linux_x64/torrent2http -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/bin/linux_x86/torrent2http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/bin/linux_x86/torrent2http -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/bin/windows_x64/torrent2http.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/bin/windows_x64/torrent2http.exe -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/bin/windows_x86/torrent2http.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/bin/windows_x86/torrent2http.exe -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | class Notify(Exception): 3 | """ 4 | This class are use for no fatal error. 5 | Normally, this means that the user must take action or similar to continue. Eg. Change a setting 6 | """ 7 | def __init__(self, logMessage, messageID=0, message=None, level=0): 8 | # An English explanation which is used for logging 9 | self.logMessage = logMessage 10 | 11 | # Message identifier which can be translated into a local language message to the user 12 | self.messageID = messageID 13 | 14 | # Message identifier which are translated into a local language to the user 15 | self.message = message 16 | 17 | self.level = level 18 | 19 | def __str__(self): 20 | return self.logMessage 21 | 22 | class Error(Exception): 23 | def __init__(self, tracebackStr, messageID): 24 | # An English explanation for use in traceback 25 | self.tracebackstr = tracebackStr 26 | 27 | # Message identifier which can be translated into a local language message to the user 28 | self.messageID = messageID 29 | 30 | def __str__(self): 31 | return self.tracebackstr 32 | 33 | class ProxyError(Error): 34 | pass 35 | 36 | class HTTPError(Error): 37 | pass 38 | 39 | class TorrentError(Error): 40 | def __init__(self, tracebackStr, messageID=None): 41 | # An English explanation for use in traceback 42 | self.tracebackstr = tracebackStr 43 | 44 | # Message identifier which can be translated into a local language message to the user 45 | self.messageID = messageID or 30313 46 | 47 | class ClassError(Exception): 48 | pass 49 | 50 | class Abort(Exception): 51 | pass 52 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/favourites.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib2 3 | import os 4 | import xbmc 5 | import xbmcaddon 6 | import hashlib 7 | 8 | from kodipopcorntime.logging import log 9 | 10 | 11 | __addon__ = xbmcaddon.Addon() 12 | __addonname__ = __addon__.getAddonInfo('name') 13 | __addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile')) 14 | 15 | _json_file = os.path.join(__addondir__, 'favourites.json') 16 | _json_movie_file = os.path.join(__addondir__, 'test.json') 17 | 18 | _skeleton = { 19 | "movies": [], 20 | "tvshows": [], 21 | "anime": [], 22 | } 23 | 24 | 25 | def get_favourites_from_file(): 26 | """Gets all favourites from the JSON file. 27 | 28 | If the JSON file does not exist, a skeleton file is generated. 29 | """ 30 | if not os.path.isfile(_json_file): 31 | create_skeleton_file() 32 | 33 | with open(_json_file) as json_read: 34 | return json.load(json_read) 35 | 36 | 37 | def set_favourites_to_file(favourites): 38 | """Overwrites the JSON file with the `favourites` dictionary""" 39 | with open(_json_file, mode='w') as json_write: 40 | json_write.write(json.dumps(favourites, indent=3)) 41 | 42 | 43 | def create_skeleton_file(): 44 | set_favourites_to_file(_skeleton) 45 | log("(Favourites) File created") 46 | 47 | 48 | def _add_to_favs(mediatype, data): 49 | favourites = get_favourites_from_file() 50 | log("(Favourites) _add_to_favs %s" % favourites) 51 | 52 | string1 ={'action': 'watch_list', 'categ': 'movie_test', 'order': '-1'} 53 | filename = 'movies.browse.%s' %hashlib.md5(str(string1)).hexdigest() 54 | log("(Favourites) filename %s" % filename) 55 | try: 56 | os.remove(os.path.join(__addondir__, 'cache', filename)) 57 | except: 58 | pass 59 | 60 | add_favourite_by_type(new_fav_id=data, fav_type=mediatype, favourites=favourites) 61 | 62 | log("(Favourites2) _add_to_favs %s" % favourites) 63 | set_favourites_to_file(favourites) 64 | 65 | xbmc.executebuiltin('Notification(%s, %s favourite has been added, 5000)' % (__addonname__, mediatype)) 66 | 67 | 68 | def _remove_from_favs(mediatype, data): 69 | favourites = get_favourites_from_file() 70 | log("(Favourites) _remove_from_favs %s" % favourites) 71 | 72 | string1 ={'action': 'watch_list', 'categ': 'movie_test', 'order': '-1'} 73 | filename = 'movies.browse.%s' %hashlib.md5(str(string1)).hexdigest() 74 | log("(Favourites) filename %s" % filename) 75 | try: 76 | os.remove(os.path.join(__addondir__, 'cache', filename)) 77 | except: 78 | pass 79 | 80 | if data == 'all': 81 | _clear_favourites(mediatype) 82 | else: 83 | remove_favourite_by_type(fav_id=data, fav_type=mediatype, favourites=favourites) 84 | log("(Favourites2) _remove_from_favs %s" % favourites) 85 | set_favourites_to_file(favourites) 86 | 87 | xbmc.executebuiltin('Notification(%s, %s favourite has been removed, 5000)' % (__addonname__, mediatype)) 88 | if mediatype == 'movies': 89 | pass 90 | else: 91 | xbmc.executebuiltin('Container.Refresh') 92 | 93 | 94 | def _get_favs(mediatype): 95 | favourites = get_favourites_from_file() 96 | log("(Favourites) _get_favs %s" % favourites) 97 | return favourites.get(mediatype) 98 | 99 | 100 | def _clear_favourites(mediatype): 101 | if mediatype == 'all': 102 | create_skeleton_file() 103 | return 104 | 105 | # Get the existing favourites 106 | favourites = get_favourites_from_file() 107 | # Overwrite the favourites for the specified mediatype with an empty list 108 | favourites[mediatype] = [] 109 | # Save changes to the JSON file 110 | set_favourites_to_file(favourites) 111 | 112 | 113 | def add_favourite_by_type(new_fav_id, fav_type, favourites): 114 | """ 115 | Modifies the `favourites` dictionary by adding the `new_fav_id` to its 116 | `fav_type` inner list, if it is not already a favourite. 117 | 118 | Examples of `fav_type` are: 'movies', 'tvshows', 'anime' 119 | """ 120 | type_favourites = favourites.get(fav_type) 121 | 122 | if all(fav['id'] != new_fav_id for fav in type_favourites): 123 | type_favourites.append({'id': str(new_fav_id)}) 124 | 125 | 126 | def remove_favourite_by_type(fav_id, fav_type, favourites): 127 | """ 128 | Modifies the `favourites` dictionary by removing the `fav_id` from its 129 | `fav_type` inner list. 130 | 131 | Examples of `fav_type` are: 'movies', 'tvshows', 'anime' 132 | """ 133 | favourites[fav_type] = filter( 134 | lambda fav: fav['id'] != fav_id, 135 | favourites.get(fav_type), 136 | ) 137 | 138 | def _create_movie_favs(): 139 | movie_favs = _get_favs('movies') 140 | log("(Favourites_movie) %s" % movie_favs) 141 | movie_favs1 = [] 142 | for fav in movie_favs: 143 | log("(Favourites_movie) %s" % fav) 144 | search = '%s/movie/%s' % ('https://popcorn-ru.tk', fav['id']) 145 | req = urllib2.Request(search, headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", "Accept-Encoding": "none"}) 146 | response = urllib2.urlopen(req) 147 | movie_fav = json.loads(response.read()) 148 | movie_favs1.append(movie_fav) 149 | log("(Favourites_movie) %s" % movie_favs1) 150 | with open(_json_movie_file, mode='w') as json_write: 151 | json_write.write(json.dumps(movie_favs1)) 152 | log("(Favourites_movie) %s" % movie_favs1) 153 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/__init__.py: -------------------------------------------------------------------------------- 1 | from .browse import Browse as browse 2 | from .folders import Folders as folders 3 | from .index import Index as index 4 | from .player import Player as player 5 | from .search import Search as search 6 | from . import cmd 7 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from kodipopcorntime import settings 3 | 4 | class _Base(object): 5 | def __init__(self, mediaType): 6 | self.interfaceName = self.__class__.__name__ 7 | 8 | self.mediaSettings = None 9 | if mediaType: 10 | self.mediaSettings = getattr(settings, mediaType) 11 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/base2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import urllib, xbmcplugin 3 | from .base import _Base 4 | from kodipopcorntime.settings import addon as _settings 5 | from kodipopcorntime.logging import log, LOGLEVEL 6 | from kodipopcorntime.utils import xbmcItem, cleanDebris 7 | 8 | class _Base2(_Base): 9 | def __init__(self, *args): 10 | super(_Base2, self).__init__(*args) 11 | cleanDebris() 12 | 13 | def createUrl(self, endpoint, mediaType=None, **params): 14 | if not mediaType: 15 | mediaType = self.mediaSettings.mediaType 16 | return "%s?%s" %(_settings.base_url, urllib.urlencode(dict([('mediaType', mediaType), ('endpoint', endpoint)], **params))) 17 | 18 | def addItem(self, item, path, isFolder=True): 19 | log("(%s) Adding item '%s'" %(self.interfaceName, item["label"])) 20 | 21 | # Ensure fanart 22 | if not item.setdefault("properties", {}).get("fanart_image"): 23 | item["properties"]["fanart_image"] = _settings.fanart 24 | 25 | xbmcplugin.addDirectoryItem(_settings.handle, path, xbmcItem(**item), isFolder) 26 | 27 | def addItems(self, items, endpoint=None, isFolder=True): 28 | agrs = () 29 | if endpoint: 30 | agrs = (endpoint,) 31 | for item in items: 32 | self.addItem(item, self.createUrl(*agrs, **item.pop('params')), isFolder) 33 | 34 | def finish(self, contentType='files', updateListing=False, cacheToDisc=True): 35 | log("(%s) Finish" %self.interfaceName, LOGLEVEL.INFO) 36 | xbmcplugin.setContent(_settings.handle, contentType) 37 | xbmcplugin.endOfDirectory(_settings.handle, True, updateListing, cacheToDisc) 38 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/base3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys, os, xbmc, xbmcplugin 3 | from .base2 import _Base2 4 | from kodipopcorntime.settings import BUILD, QUALITIES, addon as _settings 5 | from kodipopcorntime.logging import log, log_error, LOGLEVEL 6 | from kodipopcorntime.utils import xbmcItem, clear_cache 7 | from kodipopcorntime import favourites 8 | 9 | __addon__ = sys.modules['__main__'].__addon__ 10 | 11 | class _Base3(_Base2): 12 | def __init__(self, *args): 13 | super(_Base3, self).__init__(*args) 14 | try: 15 | update_id = "%s.%s" %(_settings.version, BUILD) 16 | if not update_id == _settings.last_update_id: 17 | # Clear cache after update 18 | clear_cache() 19 | __addon__.setSetting("last_update_id", update_id) 20 | except: 21 | log_error() 22 | sys.exc_clear() 23 | 24 | def getOpenSettings(self): 25 | return (__addon__.getLocalizedString(30010), 'Addon.OpenSettings(%s)' %_settings.id) 26 | 27 | def addItem(self, item, path, isFolder=True): 28 | if not isFolder: 29 | item["context_menu"] = [] 30 | log("(base3-context-menu) %s" %item, LOGLEVEL.INFO) 31 | 32 | if "info" in item and "trailer" in item["info"] and item["info"]["trailer"]: 33 | item["context_menu"] = item["context_menu"]+[('%s' %(__addon__.getLocalizedString(30038)), 'PlayMedia(%s)' %(item["info"]["trailer"]))] 34 | 35 | if "info" in item and "mediatype" in item["info"] and item["info"]["mediatype"] == 'movie': 36 | isfav = False 37 | favs = favourites._get_favs('movies') 38 | for fav in favs: 39 | if fav['id'] == item["info"]["code"]: 40 | isfav = True 41 | if isfav == True: 42 | item["context_menu"] = item["context_menu"]+[('%s' %__addon__.getLocalizedString(30042), 'RunPlugin(plugin://plugin.video.kodipopcorntime?cmd=remove_fav&action=movies&id=%s)' % (item["info"]["code"]))] 43 | item["context_menu"] = item["context_menu"]+[('%s' %__addon__.getLocalizedString(30044), 'RunPlugin(plugin://plugin.video.kodipopcorntime?cmd=remove_fav&action=movies&id=all)')] 44 | else: 45 | item["context_menu"] = item["context_menu"]+[('%s' %__addon__.getLocalizedString(30041), 'RunPlugin(plugin://plugin.video.kodipopcorntime?cmd=add_fav&action=movies&id=%s)' % (item["info"]["code"]))] 46 | 47 | for _q in QUALITIES: 48 | if "&%s=" %_q in path or "?%s=" %_q in path: 49 | log("(base3-context) %s" %_q, LOGLEVEL.INFO) 50 | item["context_menu"] = item["context_menu"]+[('%s %s' %(__addon__.getLocalizedString(30009), _q), 'RunPlugin(%s&quality=%s)' %(path, _q))] 51 | item["context_menu"] = item["context_menu"]+[self.getOpenSettings()] 52 | item["replace_context_menu"] = True 53 | super(_Base3, self).addItem(item, path, isFolder) 54 | 55 | def getCurPageNum(self): 56 | return int(xbmc.getInfoLabel("ListItem.Property(pageNum)") or 1) 57 | 58 | def addNextButton(self, **kwargs): 59 | log("(%s) Adding item 'Show more'" %self.interfaceName) 60 | 61 | item = { 62 | "label": __addon__.getLocalizedString(30000), 63 | "icon": os.path.join(_settings.resources_path, 'media', self.mediaSettings.mediaType, 'more.png'), 64 | "thumbnail": os.path.join(_settings.resources_path, 'media', self.mediaSettings.mediaType, 'more_thumbnail.png'), 65 | "replace_context_menu": True, 66 | "context_menu": [self.getOpenSettings()], 67 | "properties": { 68 | "fanart_image": _settings.fanart 69 | } 70 | } 71 | item.setdefault('properties').update(dict((key, str(value)) for key, value in kwargs.items() if value)) 72 | xbmcplugin.addDirectoryItem(_settings.handle, "%s?%s" %(_settings.base_url, _settings.cur_uri), xbmcItem(**item), True) 73 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/browse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys, xbmc, time, hashlib 3 | from .base3 import _Base3 4 | from contextlib import closing 5 | from kodipopcorntime import media 6 | from kodipopcorntime.settings import addon as _settings 7 | from kodipopcorntime.exceptions import Error, Abort 8 | from kodipopcorntime.logging import log, LOGLEVEL 9 | from kodipopcorntime.utils import Cache, SafeDialogProgress, isoToLang 10 | 11 | __addon__ = sys.modules['__main__'].__addon__ 12 | 13 | class Browse(_Base3): 14 | def show(self, action, **params): 15 | log("(Browse) Creating view", LOGLEVEL.INFO) 16 | log("(Browse) Action: %s" %str(dict([('action', action)], **params))) 17 | curPageNum = self.getCurPageNum() 18 | with closing(Cache("%s.browse.%s" %(self.mediaSettings.mediaType, hashlib.md5(str(dict([('action', action)], **params))).hexdigest()), ttl=24 * 3600, last_changed=self.mediaSettings.lastchanged)) as cache: 19 | # Reset page number if the user have cleaned the cache 20 | if not cache: 21 | curPageNum = 1 22 | 23 | if not cache or curPageNum > cache['curNumOfPages']: 24 | log("(Browse) Reading item cache") 25 | items = {} 26 | pages = 0 27 | 28 | with closing(SafeDialogProgress()) as dialog: 29 | dialog.create(_settings.name) 30 | dialog.update(0, __addon__.getLocalizedString(30007), ' ', ' ') 31 | 32 | _time = time.time() 33 | # Getting item list 34 | log("(Browse) Getting item list") 35 | with closing(media.List(self.mediaSettings, 'browse', *(action, curPageNum,), **params)) as medialist: 36 | while not medialist.is_done(0.100): 37 | if xbmc.abortRequested or dialog.iscanceled(): 38 | raise Abort() 39 | attempts = medialist.attempts() 40 | if attempts > 1: 41 | dialog.update(0, __addon__.getLocalizedString(30007), __addon__.getLocalizedString(30024)+str(attempts), ' ') 42 | res = medialist.get_data() 43 | if not res: 44 | raise Error("Did not receive any movies", 30305) 45 | items = res['items'] 46 | pages = res['pages'] 47 | 48 | # Update progress dialog 49 | dialog.set_mentions(len(items)+2) 50 | dialog.update(1, __addon__.getLocalizedString(30018), ' ', ' ') 51 | 52 | def on_data(progressValue, oldItem, newItem): 53 | label = ["%s %s" %(__addon__.getLocalizedString(30034), oldItem["label"])] 54 | if newItem.get("label") and not oldItem["label"] == newItem["label"]: 55 | label = label+["%s %s" %(__addon__.getLocalizedString(30035), newItem["label"])] 56 | if newItem.get("stream_info", {}).get("subtitle", {}).get("language"): 57 | label = label+["%s %s" %(__addon__.getLocalizedString(30012), isoToLang(newItem["stream_info"]["subtitle"]["language"]))] 58 | while len(label) < 3: 59 | label = label+[' '] 60 | dialog.update(progressValue, *label) 61 | 62 | # Getting media cache 63 | log("(Browse) Getting media info") 64 | with closing(media.MediaCache(self.mediaSettings, on_data)) as mediadata: 65 | [mediadata.submit(item) for item in items] 66 | mediadata.start() 67 | while not mediadata.is_done(0.100): 68 | if xbmc.abortRequested or dialog.iscanceled(): 69 | raise Abort() 70 | items = mediadata.get_data() 71 | if not items: 72 | raise Error("Did not receive any data", 30304) 73 | log("(Browse) Reading time: %s" %(time.time()-_time)) 74 | 75 | # Done 76 | dialog.update(1, __addon__.getLocalizedString(30017), ' ', ' ') 77 | 78 | log("(Browse) Updating view cache") 79 | cache.extendKey("items", items) 80 | cache.update({"curNumOfPages": curPageNum, "totalPages": pages}) 81 | pageCache = cache.copy() 82 | 83 | log("(Browse) Adding items") 84 | self.addItems(pageCache["items"], 'player', False) 85 | 86 | # NOTE: 87 | # Add show more, but we stop at page 20... yes 20 pages sounds all right... 88 | # ... each page cache file can be between 2 and 3 mByt with 20 pages and will have an average of 1 mByt... 89 | # This can become substantial problem with movies and tv-shows pages 90 | if pageCache['curNumOfPages'] < pageCache['totalPages'] and pageCache['curNumOfPages'] < 21: 91 | self.addNextButton(**{'pageNum': pageCache['curNumOfPages']+1}) 92 | 93 | update_listing = False 94 | if curPageNum > 1: 95 | update_listing = True 96 | 97 | self.finish(self.mediaSettings.mediaType, update_listing) 98 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/cmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys, os 3 | from kodipopcorntime import settings 4 | from kodipopcorntime.utils import Dialog, notify, clear_cache, cleanDebris 5 | from kodipopcorntime import favourites as _favs 6 | 7 | __addon__ = sys.modules['__main__'].__addon__ 8 | 9 | def clearCache(): 10 | if Dialog().yesno(30033): 11 | clear_cache() 12 | notify(30301) 13 | 14 | def clearMediaCache(): 15 | if Dialog().yesno(30033): 16 | cleanDebris() 17 | notify(30301) 18 | 19 | def resetTorrentSettings(): 20 | if Dialog().yesno(30013, 30014): 21 | # Network 22 | __addon__.setSetting("listen_port", '6881') 23 | __addon__.setSetting("use_random_port", 'true') 24 | __addon__.setSetting("encryption", '1') 25 | __addon__.setSetting("connections_limit", '200') 26 | # Peers 27 | __addon__.setSetting("torrent_connect_boost", '50') 28 | __addon__.setSetting("connection_speed", '50') 29 | __addon__.setSetting("peer_connect_timeout", '15') 30 | __addon__.setSetting("min_reconnect_time", '60') 31 | __addon__.setSetting("max_failcount", '3') 32 | # Features 33 | __addon__.setSetting("enable_tcp", 'true') 34 | __addon__.setSetting("enable_dht", 'true') 35 | __addon__.setSetting("enable_lsd", 'true') 36 | __addon__.setSetting("enable_utp", 'true') 37 | __addon__.setSetting("enable_scrape", 'false') 38 | __addon__.setSetting("enable_upnp", 'true') 39 | __addon__.setSetting("enable_natpmp", 'true') 40 | # Additional 41 | __addon__.setSetting("trackers", '') 42 | __addon__.setSetting("dht_routers", '') 43 | notify(30314) 44 | 45 | def add_fav(mediatype, _id): 46 | _favs._add_to_favs(mediatype, _id) 47 | 48 | def remove_fav(mediatype, _id): 49 | _favs._remove_from_favs(mediatype, _id) 50 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/folders.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from .base2 import _Base2 3 | from kodipopcorntime.logging import log, LOGLEVEL 4 | 5 | class Folders(_Base2): 6 | def show(self, action, **params): 7 | log("(Folders) Creating view", LOGLEVEL.INFO) 8 | self.addItems(self.mediaSettings.provider.folders(*(action,), **params)) 9 | self.finish() 10 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import xbmc, time 3 | from .base2 import _Base2 4 | from kodipopcorntime import settings 5 | from kodipopcorntime.exceptions import Notify, Error, Abort 6 | from kodipopcorntime.logging import log 7 | 8 | class Index(_Base2): 9 | def show(self, **params): 10 | log("(Index) Creating view") 11 | for mediaType in settings.MEDIATYPES: 12 | item = getattr(settings, ".provider" %mediaType).folders(None)[0] 13 | self.addItem(item, self.createUrl(mediaType=mediaType, **item.pop('params'))) 14 | self.finish() 15 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/player.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os, sys, xbmc, xbmcplugin, ctypes, subprocess 3 | from .base import _Base 4 | from kodipopcorntime.platform import Platform 5 | from kodipopcorntime.settings import addon as _settings 6 | from kodipopcorntime.exceptions import Notify, Error 7 | from kodipopcorntime.logging import log, LOGLEVEL 8 | from kodipopcorntime.utils import Dialog, notify, NOTIFYLEVEL, build_magnetFromMeta, shortenBytes 9 | from kodipopcorntime.torrent import TorrentPlayer 10 | 11 | __addon__ = sys.modules['__main__'].__addon__ 12 | 13 | class Player(_Base): 14 | def calculate_free_space(self): 15 | if Platform.system == 'android' and not hasattr(os, 'statvfs'): 16 | potencies = {'k':1,'M':2,'G':3,'T':4,'P':5,'E':6,'T':7,'Y':8} 17 | free = subprocess.Popen(["/system/bin/df", self.mediaSettings.download_path], stdout=subprocess.PIPE).communicate()[0].split("\n")[1].split()[3] 18 | return int(float(free[:-1])*(1024^potencies[free[-1:]])) 19 | if Platform.system == 'windows': 20 | free_bytes = ctypes.c_ulonglong(0) 21 | ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(self.mediaSettings.download_path), None, None, ctypes.pointer(free_bytes)) 22 | return free_bytes.value 23 | st = os.statvfs(self.mediaSettings.download_path) 24 | return st.f_bavail * st.f_frsize 25 | 26 | def getSelectedItem(self): 27 | castAndRole = [] 28 | _c = xbmc.getInfoLabel('ListItem.CastAndRole') 29 | if _c: 30 | castAndRole = [cr.split(' as ', 1) for cr in _c.replace('\n', ' / ').split(' / ')] 31 | 32 | return { 33 | "label": xbmc.getInfoLabel('ListItem.Label'), 34 | "icon": xbmc.getInfoLabel('ListItem.Icon'), 35 | "thumbnail": xbmc.getInfoLabel('ListItem.Thumb'), 36 | "info": { 37 | "title": xbmc.getInfoLabel('ListItem.Title'), 38 | "year": int(xbmc.getInfoLabel('ListItem.Year') or 0), 39 | "originaltitle": xbmc.getInfoLabel('ListItem.OriginalTitle'), 40 | "genre": xbmc.getInfoLabel('ListItem.Genre'), 41 | 'castandrole': castAndRole, 42 | 'director': xbmc.getInfoLabel('ListItem.Director'), 43 | "plot": xbmc.getInfoLabel('ListItem.Plot'), 44 | "plotoutline": xbmc.getInfoLabel('ListItem.PlotOutline'), 45 | "tagline": xbmc.getInfoLabel('ListItem.Tagline'), 46 | "writer": xbmc.getInfoLabel('ListItem.Writer'), 47 | "rating": float(xbmc.getInfoLabel('ListItem.Rating') or 0.0), 48 | "code": xbmc.getInfoLabel('ListItem.IMDBNumber'), 49 | "studio": xbmc.getInfoLabel('ListItem.Studio'), 50 | "votes": xbmc.getInfoLabel('ListItem.Rating') or 0.0 51 | }, 52 | "properties": { 53 | "fanart_image": xbmc.getInfoLabel("ListItem.Property(fanart_image)") 54 | }, 55 | "stream_info": { 56 | "video": { 57 | "codec": xbmc.getInfoLabel('ListItem.VideoCodec'), 58 | "width": int(xbmc.getInfoLabel('ListItem.VideoResolution')), 59 | "height": xbmc.getInfoLabel('ListItem.VideoResolution') == '1920' and 1080 or 720 60 | }, 61 | "audio": { 62 | "codec": xbmc.getInfoLabel('ListItem.AudioCodec'), 63 | "language": xbmc.getInfoLabel('ListItem.AudioLanguage'), 64 | "channels": int(xbmc.getInfoLabel('ListItem.AudioChannels') or 2) 65 | }, 66 | 'subtitle': { 67 | 'language': xbmc.getInfoLabel('ListItem.SubtitleLanguage') 68 | } 69 | } 70 | } 71 | 72 | def show(self, subtitle=None, quality=None, **params): 73 | log("(Player) Creating player options") 74 | if _settings.handle > -1: 75 | xbmcplugin.endOfDirectory(_settings.handle, True, False, False) # https://github.com/Diblo/KODI-Popcorn-Time/issues/57 76 | 77 | item = self.getSelectedItem() 78 | 79 | free_space = self.calculate_free_space() 80 | if not quality: 81 | waring = [] 82 | for _q in self.mediaSettings.qualities: 83 | if params.get(_q): 84 | if params['%ssize' %_q] > free_space: 85 | if _q == '3D' and self.mediaSettings.play3d == 1 and not Dialog().yesno(line2=30011, lineStr1=' ', headingStr=item['info']['title']): 86 | continue 87 | quality = _q 88 | break 89 | waring = waring+[_q] 90 | 91 | if waring: 92 | if not quality: 93 | raise Notify('There is not enough free space in %s' %self.mediaSettings.download_path, 30323, level=NOTIFYLEVEL.ERROR) 94 | 95 | if len(waring) > 1: 96 | notify(message=__addon__.getLocalizedString(30325) %(", ".join(waring), waring.pop()), level=NOTIFYLEVEL.WARNING) 97 | else: 98 | notify(message=__addon__.getLocalizedString(30326) %waring[0], level=NOTIFYLEVEL.WARNING) 99 | log('(Player) There must be a minimum of %s to play. %s available in %s' %(shortenBytes(params['%ssize' %quality]), shortenBytes(free_space), self.mediaSettings.download_path), LOGLEVEL.NOTICE) 100 | 101 | elif not params.get(quality): 102 | raise Error('%s quality was not found' %quality, 30023) 103 | elif params['%ssize' %quality] < free_space: 104 | raise Notify('There is not enough free space in %s' %self.mediaSettings.download_path, 30323, level=NOTIFYLEVEL.ERROR) 105 | 106 | TorrentPlayer().playTorrentFile(self.mediaSettings, build_magnetFromMeta(params[quality], "quality %s" %quality), item, subtitle) 107 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/gui/search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys, xbmc, time 3 | from .base3 import _Base3 4 | from contextlib import closing 5 | from kodipopcorntime import media 6 | from kodipopcorntime.exceptions import Notify, Error, Abort 7 | from kodipopcorntime.logging import log, LOGLEVEL 8 | from kodipopcorntime.utils import SafeDialogProgress, Cache, isoToLang, NOTIFYLEVEL 9 | 10 | __addon__ = sys.modules['__main__'].__addon__ 11 | 12 | class Search(_Base3): 13 | def getSearchString(self): 14 | log("(Search) Getting search string") 15 | string = xbmc.getInfoLabel("ListItem.Property(searchString)") 16 | if not string: 17 | log("(Search) Showing keyboard") 18 | keyboard = xbmc.Keyboard('', __addon__.getLocalizedString(30001), False) 19 | keyboard.doModal() 20 | if not keyboard.isConfirmed() or not keyboard.getText(): 21 | raise Abort() 22 | string = keyboard.getText() 23 | log("(Search) Returning search string '%s'" %string) 24 | return string 25 | 26 | def show(self, **params): 27 | log("(Search) Creating view", LOGLEVEL.INFO) 28 | searchString = self.getSearchString() 29 | 30 | curPageNum = self.getCurPageNum() 31 | with closing(Cache("%s.search.query" %self.mediaSettings.mediaType, ttl=24 * 3600, last_changed=self.mediaSettings.lastchanged)) as cache: 32 | # Reset cache when we have different search string 33 | if cache and not searchString == cache['searchString']: 34 | log("(Search) Resetting view cache") 35 | cache.trunctate() 36 | 37 | # Reset page number if the user have cleaned the cache 38 | # or we have a different search string 39 | if not cache: 40 | curPageNum = 1 41 | 42 | if not cache or curPageNum > cache['curNumOfPages']: 43 | log("(Search) Reading item cache") 44 | items = {} 45 | pages = 0 46 | 47 | with closing(SafeDialogProgress()) as dialog: 48 | dialog.create(__addon__.getLocalizedString(30028)) 49 | dialog.update(0, __addon__.getLocalizedString(30007), ' ', ' ') 50 | 51 | _time = time.time() 52 | # Getting item list 53 | log("(Search) Getting item list") 54 | with closing(media.List(self.mediaSettings, 'search', *(searchString, curPageNum,), **params)) as medialist: 55 | while not medialist.is_done(0.100): 56 | if xbmc.abortRequested or dialog.iscanceled(): 57 | raise Abort() 58 | attempts = medialist.attempts() 59 | if attempts > 1: 60 | dialog.update(0, __addon__.getLocalizedString(30007), __addon__.getLocalizedString(30024)+str(attempts), ' ') 61 | res = medialist.get_data() 62 | if not res: 63 | raise Notify("No search result", 30327, NOTIFYLEVEL.INFO) 64 | items = res['items'] 65 | pages = res['pages'] 66 | 67 | # Update progress dialog 68 | dialog.set_mentions(len(items)+2) 69 | dialog.update(1, __addon__.getLocalizedString(30018), ' ', ' ') 70 | 71 | def on_data(progressValue, oldItem, newItem): 72 | label = ["%s %s" %(__addon__.getLocalizedString(30034), oldItem["label"])] 73 | if newItem.get("label") and not oldItem["label"] == newItem["label"]: 74 | label = label+["%s %s" %(__addon__.getLocalizedString(30035), newItem["label"])] 75 | if newItem.get("stream_info", {}).get("subtitle", {}).get("language"): 76 | label = label+["%s %s" %(__addon__.getLocalizedString(30012), isoToLang(newItem["stream_info"]["subtitle"]["language"]))] 77 | while len(label) < 3: 78 | label = label+[' '] 79 | dialog.update(progressValue, *label) 80 | 81 | # Getting media cache 82 | log("(Search) Getting media info") 83 | with closing(media.MediaCache(self.mediaSettings, on_data)) as mediadata: 84 | [mediadata.submit(item) for item in items] 85 | mediadata.start() 86 | while not mediadata.is_done(0.100): 87 | if xbmc.abortRequested or dialog.iscanceled(): 88 | raise Abort() 89 | items = mediadata.get_data() 90 | if not items: 91 | raise Error("Did not receive any data", 30304) 92 | log("(Search) Reading time: %s" %(time.time()-_time)) 93 | 94 | # Done 95 | dialog.update(1, __addon__.getLocalizedString(30017), ' ', ' ') 96 | 97 | log("(Search) Updating view cache") 98 | cache.extendKey("items", items) 99 | cache.update({"curNumOfPages": curPageNum, "totalPages": pages, "searchString": searchString}) 100 | pageCache = cache.copy() 101 | 102 | log("(Search) Adding items") 103 | self.addItems(pageCache["items"], 'player', False) 104 | 105 | # NOTE: 106 | # Add show more, but we stop at page 20... yes 20 pages sounds all right... 107 | # ... each page cache file can be between 2 and 3 mByt with 20 pages and will have an average of 1 mByt... 108 | # This can become substantial problem with movies and tv-shows pages 109 | if pageCache['curNumOfPages'] < pageCache['totalPages'] and pageCache['curNumOfPages'] < 21: 110 | self.addNextButton(**{'pageNum': pageCache['curNumOfPages']+1, 'searchString': searchString}) 111 | 112 | update_listing = False 113 | if curPageNum > 1: 114 | update_listing = True 115 | 116 | self.finish(self.mediaSettings.mediaType, update_listing) 117 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/logging.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os, re, xbmc, traceback, sys 3 | from kodipopcorntime.threads import Thread 4 | __addon__ = sys.modules['__main__'].__addon__ 5 | 6 | _id = __addon__.getAddonInfo('id') 7 | _loglevel = [xbmc.LOGDEBUG, xbmc.LOGINFO, xbmc.LOGNOTICE, xbmc.LOGWARNING, xbmc.LOGERROR, xbmc.LOGFATAL, -1] 8 | if __addon__.getSetting("debug") == 'true': 9 | _loglevel[6] = xbmc.LOGDEBUG 10 | 11 | class LOGLEVEL: 12 | NONE = -1 13 | DEBUG = 0 14 | INFO = 1 15 | NOTICE = 2 16 | WARNING = 3 17 | ERROR = 4 18 | FATAL = 5 19 | 20 | def prefix(): 21 | if hasattr(Thread.LOCAL, 'tName'): 22 | return "(%s) " %Thread.LOCAL.tName 23 | return '' 24 | 25 | def log(message, level=LOGLEVEL.DEBUG): 26 | level = _loglevel[level] 27 | if level > -1: 28 | if isinstance(message, str): 29 | message = unicode(message, errors='ignore') 30 | xbmc.log(msg=u'[%s] %s%s' %(_id, prefix(), message), level=level) 31 | 32 | def log_error(): 33 | xbmc.log(msg=u'[%s] %s%s' %(_id, prefix(), unicode(traceback.format_exc(), errors='ignore')), level=_loglevel[LOGLEVEL.FATAL]) 34 | 35 | class LogPipe(Thread): 36 | def __init__(self, logger): 37 | self._logger = logger 38 | self._read_fd, self._write_fd = os.pipe() 39 | super(LogPipe, self).__init__(target=self.run) 40 | 41 | def fileno(self): 42 | return self._write_fd 43 | 44 | def run(self): 45 | self._logger("Logging started") 46 | with os.fdopen(self._read_fd) as f: 47 | for line in iter(f.readline, ""): 48 | line = re.sub(r'^\d+/\d+/\d+ \d+:\d+:\d+ ', '', line) 49 | self._logger(line.strip()) 50 | if self.stop.is_set(): 51 | break 52 | self._logger("Logging finished") 53 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/media.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import hashlib, time, sys 3 | from kodipopcorntime.logging import log, LOGLEVEL, log_error 4 | from kodipopcorntime.utils import cleanDictList, Cache 5 | from kodipopcorntime.request import Json 6 | from kodipopcorntime.settings import addon as _settings 7 | from kodipopcorntime.threads import Thread 8 | 9 | _ttl = 5 * 24 * 3600 10 | 11 | class _Base(Thread): 12 | def __init__(self): 13 | log("(Media) Staring thread") 14 | self._request = Json() 15 | super(_Base, self).__init__(target=self._run) 16 | 17 | def is_done(self, wait=0): 18 | time.sleep(wait) 19 | return self.stop.is_set() 20 | 21 | def _handle_param(self, **param): 22 | try: 23 | return self._request.request(**param) 24 | except TypeError: 25 | return self._request.request_proxy(**param) 26 | 27 | def close(self): 28 | super(_Base, self).close() 29 | self._request.close() 30 | 31 | class List(_Base): 32 | def __init__(self, mediaSettings, call, *args, **kwargs): 33 | self._provider_fn = getattr(mediaSettings.provider, call) 34 | self._provider_build_fn = getattr(mediaSettings.provider, "%s_build" %call) 35 | self._call = call 36 | self._args = args 37 | self._kwargs = kwargs 38 | self._data = {} 39 | self._proxy_attempt = 0 40 | self._domaine = None 41 | 42 | super(List, self).__init__() 43 | 44 | def attempts(self): 45 | if not self._domaine == self._request.netloc: 46 | self._proxy_attempt = self._proxy_attempt+1 47 | self._domaine = self._request.netloc 48 | return self._proxy_attempt 49 | 50 | def get_data(self, wait=0): 51 | if self.is_done(wait): 52 | return self._data 53 | return {} 54 | 55 | def _run(self): 56 | log("(Media) Getting list", LOGLEVEL.INFO) 57 | _res = self._handle_param(**self._provider_fn(*self._args, **self._kwargs)) 58 | if not self.stop.is_set(): 59 | self._data = self._provider_build_fn(_res, *self._args, **self._kwargs) # 1. Get request parameter. 2. Perform request. 3. Build item. 60 | self.close() 61 | 62 | class MediaCache: 63 | def __init__(self, mediaSettings, callback=None, workers=2): 64 | log("(Media) Initialize media cache", LOGLEVEL.INFO) 65 | self._mediaSettings = mediaSettings 66 | self._callbackfn = callback 67 | self._workers = workers or 2 68 | 69 | self._indexCount = 0 70 | self._data = [] 71 | self._queue = [] 72 | self._queueStart = False 73 | self._threads = [] 74 | self._preloader = None 75 | self._progressValue = 1 76 | 77 | if self._mediaSettings.metadata_provider or self._mediaSettings.subtitles_provider: 78 | self._preloader = _Preloader(self._mediaSettings) 79 | self._progressValue = self._progressValue/(bool(self._mediaSettings.metadata_provider)+(not self._mediaSettings.metadata_provider.FALLBACKLANG == _settings.language)+bool(self._mediaSettings.subtitles_provider)+0.0) 80 | 81 | def __enter__(self): 82 | return self 83 | 84 | def submit(self, item): 85 | if not self._queueStart: 86 | log("(Media) Submit '%s'" %item['label']) 87 | self._queue.append([self._indexCount, item]) 88 | self._indexCount = self._indexCount+1 89 | 90 | def start(self): 91 | if not self._queueStart and len(self._queue) > 0: 92 | self._queueStart = True 93 | while self._workers > len(self._threads): 94 | self._threads.append(_Dispenser(self._mediaSettings, self._queue, self._data, self._preloader, self._progressValue, self._callbackfn)) 95 | 96 | def is_done(self, wait=0): 97 | time.sleep(wait) 98 | return all(thread.is_done() for thread in self._threads) 99 | 100 | def get_data(self, wait=0): 101 | if self.is_done(wait): 102 | return self._data 103 | return [] 104 | 105 | def __exit__(self, *exc_info): 106 | self.close() 107 | return not exc_info[0] 108 | 109 | def __del__(self): 110 | if hasattr(self, '_progressValue'): 111 | self.close() 112 | 113 | def close(self): 114 | if self._preloader: 115 | self._preloader.close() 116 | self._preloader = None 117 | for thread in self._threads: 118 | thread.close() 119 | self._threads = [] 120 | 121 | class _Preloader(_Base): 122 | def __init__(self, mediaSettings): 123 | self._mediaSettings = mediaSettings 124 | super(_Preloader, self).__init__() 125 | 126 | def _run(self): 127 | log("(Media) Running preloader", LOGLEVEL.INFO) 128 | if self._mediaSettings.metadata_provider: 129 | _res = self._handle_params(self._mediaSettings.metadata_provider.pre()) 130 | if not self.stop.is_set(): 131 | self._mediaSettings.metadata_provider.build_pre(_res) 132 | if self._mediaSettings.subtitles_provider and not self.stop.is_set(): 133 | _res = self._handle_params(self._mediaSettings.subtitles_provider.pre()) 134 | if not self.stop.is_set(): 135 | self._mediaSettings.subtitles_provider.build_pre(_res) 136 | log("(Media) Preloader done") 137 | self.close() 138 | 139 | def _handle_params(self, list): 140 | if not list: 141 | return [] 142 | _d = [] 143 | for p in list: 144 | try: 145 | _d.append(self._request.request(**p)) 146 | except TypeError: 147 | _d.append(self._request.request_proxy(**p)) 148 | return _d 149 | 150 | class _Dispenser(_Base): 151 | def __init__(self, mediaSettings, queue, data, preloader, progressValue, callback=None): 152 | self._mediaSettings = mediaSettings 153 | self._queue = queue 154 | self._data = data 155 | self._preloader = preloader 156 | self._progressValue = progressValue 157 | self._callbackfn = callback 158 | super(_Dispenser, self).__init__() 159 | 160 | def _run(self): 161 | log("(Media) Initialize queue") 162 | if self._preloader: 163 | # Waiting on the preloader to finish 164 | while not self._preloader.is_done(0.100): 165 | pass 166 | if self._preloader.checkError(): 167 | self.close() 168 | 169 | # Queue 170 | log("(Media) Starting queue", LOGLEVEL.INFO) 171 | while not self.stop.is_set(): 172 | try: 173 | jobId, item = self._queue.pop(0) 174 | 175 | if self._preloader: # No peloader is the same as no provider 176 | id = hashlib.md5(str(item)).hexdigest() 177 | args = [item.get("info", {}).get("code"), item["label"], item.get("info", {}).get("year")] 178 | if self._mediaSettings.metadata_provider: 179 | self._getMeta(item, args, id) 180 | if self._mediaSettings.subtitles_provider: 181 | self._getSubtitle(item, args, id) 182 | else: 183 | if self._callbackfn: 184 | log("(Media) Callback with '%s'" %item['label']) 185 | self._callbackfn(self._progressValue, item, item) 186 | self._data.insert(jobId, item) 187 | 188 | except IndexError: 189 | sys.exc_clear() 190 | self.close() 191 | 192 | def _getMeta(self, item, args, id): 193 | metadata = Cache("%s.mediainfo.metadata" %self._mediaSettings.mediaType, ttl=_ttl, readOnly=True, last_changed=self._mediaSettings.metadata_lastchanged).get(id) 194 | if not metadata: 195 | try: 196 | log("(Media) Getting metadata") 197 | _res = self._handle_param(**self._mediaSettings.metadata_provider.item(*args+[_settings.language])) 198 | if not self.stop.is_set(): 199 | metadata = cleanDictList(self._mediaSettings.metadata_provider.build_item(_res, *args+[_settings.language])) # 1. Get request parameter. 2. Perform request(s). 3. Build info. 200 | except: 201 | log_error() 202 | sys.exc_clear() 203 | finally: 204 | if self._callbackfn: 205 | log("(Media) Callback with '%s'" %item['label']) 206 | self._callbackfn(self._progressValue, item, metadata or {}) 207 | 208 | if not self._mediaSettings.metadata_provider.FALLBACKLANG == _settings.language: 209 | fallbackMeta = None 210 | try: 211 | log("(Media) Getting fallback metadata") 212 | _res = self._handle_param(**self._mediaSettings.metadata_provider.item(*args+[_settings.language])) 213 | if not self.stop.is_set(): 214 | fallbackMeta = self._mediaSettings.metadata_provider.build_item(_res, *args+[_settings.language]) # 1. Get request parameter. 2. Perform request(s). 3. Build info. 215 | except: 216 | log_error() 217 | sys.exc_clear() 218 | else: 219 | if metadata and fallbackMeta: 220 | fallbackMeta.update(metadata) 221 | if fallbackMeta: 222 | metadata = cleanDictList(fallbackMeta) 223 | finally: 224 | if self._callbackfn: 225 | log("(Media) Callback with '%s'" %item['label']) 226 | self._callbackfn(self._progressValue, item, metadata or {}) 227 | 228 | if metadata: 229 | Cache("%s.mediainfo.metadata" %self._mediaSettings.mediaType, ttl=_ttl, last_changed=self._mediaSettings.metadata_lastchanged)[id] = metadata 230 | else: 231 | if self._callbackfn: 232 | log("(Media) Callback with '%s'" %item['label']) 233 | self._callbackfn(self._progressValue*(1+(not self._mediaSettings.metadata_provider.FALLBACKLANG == _settings.language)), item, metadata) 234 | 235 | if metadata: 236 | item.setdefault('info', {}).update(dict((key, value) for key, value in metadata.pop('info', {}).items() if value)) 237 | item.setdefault('properties', {}).update(dict((key, value) for key, value in metadata.pop('properties', {}).items() if value)) 238 | item.setdefault('stream_info', {}).setdefault('video', {}).update(dict((key, value) for key, value in metadata.pop('stream_info', {}).pop('video', {}).items() if value)) 239 | item.update(dict((key, value) for key, value in metadata.items() if value)) 240 | 241 | def _getSubtitle(self, item, args, id): 242 | log("(Media) Subtitle") 243 | subtitle = Cache("%s.mediainfo.subtitles" %self._mediaSettings.mediaType, ttl=_ttl, readOnly=True, last_changed=self._mediaSettings.subtitle_lastchanged).get(id) 244 | if not subtitle: 245 | try: 246 | log("(Media) Getting subtitle") 247 | _res = self._handle_param(**self._mediaSettings.subtitles_provider.item(*args)) 248 | if not self.stop.is_set(): 249 | subtitle = cleanDictList(self._mediaSettings.subtitles_provider.build_item(_res, *args)) # 1. Get request parameter. 2. Perform request(s). 3. Build info. 250 | except: 251 | log_error() 252 | sys.exc_clear() 253 | else: 254 | if subtitle: 255 | Cache("%s.mediainfo.subtitles" %self._mediaSettings.mediaType, ttl=_ttl, last_changed=self._mediaSettings.subtitle_lastchanged)[id] = subtitle 256 | 257 | if self._callbackfn: 258 | log("(Media) Callback with '%s'" %item['label']) 259 | self._callbackfn(self._progressValue, item, subtitle or {}) 260 | 261 | if subtitle: 262 | item.setdefault('stream_info', {})['subtitle'] = subtitle.setdefault('stream_info', {}).get("subtitle", {}) 263 | item.setdefault('params', {}).update(subtitle.get('params', {})) 264 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/platform.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import sys 4 | 5 | 6 | class Platform(object): 7 | class __metaclass__(type): 8 | def __getattr__(cls, name): 9 | getattr(cls, "_%s" % name)() 10 | return getattr(cls, name) 11 | 12 | def _arch(cls): 13 | if sys.platform.lower().startswith('linux') and (os.uname()[4].lower().startswith('arm') or os.uname()[4].lower().startswith('aarch')): 14 | cls.arch = 'arm' 15 | elif sys.maxsize > 2**32 or sys.platform.lower().startswith('linux') and os.uname()[4].lower().startswith('x86_64'): 16 | cls.arch = 'x64' 17 | elif sys.platform.lower().startswith('win'): 18 | cls.arch = 'x86' 19 | else: 20 | cls.arch = 'x86' 21 | 22 | def _system(cls): 23 | if sys.platform.lower().startswith('linux'): 24 | cls.system = 'linux' 25 | if 'ANDROID_DATA' in os.environ: 26 | cls.system = 'android' 27 | elif sys.platform.lower().startswith('win'): 28 | cls.system = 'windows' 29 | elif sys.platform.lower().startswith('darwin'): 30 | cls.system = 'darwin' 31 | else: 32 | cls.system = None 33 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/api-fetch.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import xbmc 4 | import xbmcaddon 5 | from kodipopcorntime import settings 6 | from .apifetch import movies as api_movies 7 | from .apifetch import tvShows as api_tvShows 8 | from .apifetch import anime as api_anime 9 | from kodipopcorntime.logging import log, LOGLEVEL 10 | from kodipopcorntime import favourites as _favs 11 | __addon__ = sys.modules['__main__'].__addon__ 12 | 13 | _categories = { 14 | '30350': 'Movies', 15 | '30351': 'TVShows', 16 | '30352': 'Anime' 17 | } 18 | 19 | __addon__ = xbmcaddon.Addon() 20 | __addonname__ = __addon__.getAddonInfo('name') 21 | __addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile')) 22 | 23 | _json_file = os.path.join(__addondir__, 'test.json') 24 | 25 | _proxy_identifier = 'api-fetch.proxies' 26 | 27 | def _getDomains(): 28 | domains = [ 29 | # Currently working, doesn't support anime yet 30 | "https://popcorn-ru.tk" 31 | ] 32 | 33 | # User domains have highest priority 34 | return settings.movies.proxies+domains 35 | 36 | def folders(action, **kwargs): 37 | '''folders are used to create lists of genres, shows, animes and seasons 38 | :param action: (string) When index is call, this parameter will be empty, then set an operation. 39 | :param kwargs: (dict) When index is call, this parameter will be empty, then contains the user parameters. 40 | :return: (list) Return a list with items. (Only the first item are used when index is call.) 41 | ''' 42 | 43 | _dom = _getDomains() 44 | 45 | if action == 'categories': 46 | '''Action categories creates list of categories ''' 47 | items= [] 48 | for n in __addon__.getLocalizedString(30353).split(','): 49 | if _categories.get(n): 50 | path = os.path.join(settings.addon.resources_path, 'media', 'categories', '%s.png' %_categories[n]) 51 | items.append({ 52 | "label": __addon__.getLocalizedString(int(n)), 53 | "icon": path, 54 | "thumbnail": path, 55 | "params": { 56 | "endpoint": "folders", # endpoint is required 57 | 'action': "cat_%s" %_categories[n] # action carries an operation 58 | } 59 | }) 60 | return items 61 | 62 | if action == 'cat_Movies': 63 | '''Action cat_Movies creates a list of options for movies ''' 64 | return api_movies._folders(action) 65 | 66 | if action == 'genres_movies': 67 | '''Action genres_movies creates a list of genres''' 68 | return api_movies._folders(action) 69 | 70 | if action == 'favourites_movies': 71 | '''Action favorites_TV-Shows gets saved favourites for TV Shows''' 72 | return api_movies._favourites(_dom, **kwargs) 73 | 74 | if action == 'cat_TVShows': 75 | '''Action cat_TVShows creates a list of options for TV Shows ''' 76 | return api_tvShows._folders(action) 77 | 78 | if action == 'genres_TV-shows': 79 | '''Action genres_movies creates a list of genres''' 80 | return api_tvShows._folders(action) 81 | 82 | if action == 'favorites_TV-Shows': 83 | '''Action favorites_TV-Shows gets saved favourites for TV Shows''' 84 | return api_tvShows._favourites(_dom, **kwargs) 85 | 86 | if action == 'cat_Anime': 87 | '''Action cat_TVShows creates a list of options for TV Shows ''' 88 | return api_anime._folders(action) 89 | 90 | if action == 'genres_anime': 91 | '''Action genres_anime creates a list of genres''' 92 | return api_anime._folders(action) 93 | 94 | if action == 'favorites_Anime': 95 | '''Action favorites_TV-Shows gets saved favourites for TV Shows''' 96 | return api_anime._favourites(_dom, **kwargs) 97 | 98 | 99 | if action == 'show-list': 100 | '''Action show-list creates a list of TV Shows''' 101 | return api_tvShows._shows(_dom, **kwargs) 102 | 103 | if action == 'show-seasons': 104 | '''Action show-seasons creates a list of seasons for a TV Show''' 105 | return api_tvShows._seasons(_dom, **kwargs) 106 | 107 | if action == 'anime-list': 108 | '''Action anime-list creates a list of Animes''' 109 | return api_anime._shows(_dom, **kwargs) 110 | 111 | if action == 'anime-seasons': 112 | '''Action anime-seasons creates a list of seasons for a Animes''' 113 | return api_anime._seasons(_dom, **kwargs) 114 | 115 | # There was no action, therefore has index be called 116 | return [{ 117 | "label": __addon__.getLocalizedString(30030), # "label" is require 118 | "icon": os.path.join(settings.addon.resources_path, 'media', 'movies.png'), 119 | "thumbnail": os.path.join(settings.addon.resources_path, 'media', 'movies.png'), 120 | "params": { 121 | "endpoint": "folders", # "endpoint" is require 122 | 'action': "categories" # Require when calling browse or folders (Action is used to separate the content) 123 | } 124 | }] 125 | 126 | def browse(action, page, **kwargs): 127 | '''browse are used to returning parameters used for 'Request' when a movie or show episodes list is displayed. 128 | :param action: (string) This parameter set an operation 129 | :param page: (int) Contains the current page number 130 | :param kwargs: (dict) Contain user parameters 131 | :return: (dict) Return parameters used for 'Request' 132 | ''' 133 | if kwargs['categ'] == 'movies': 134 | return { 135 | 'proxies': _getDomains(), 136 | 'path': "/movies/%s" %page, 137 | 'params': { 138 | 'genre': action == 'genre' and kwargs['genre'] or 'all', 139 | 'sort': action == 'genre' and "seeds" or action, 140 | 'order': kwargs['order'] 141 | }, 142 | 'proxyid': _proxy_identifier 143 | } 144 | else: 145 | if kwargs['categ'] == 'movie_test': 146 | return { 147 | 'proxies': '', 148 | 'path': 'movie_favs', 149 | 'params': { 150 | 151 | }, 152 | 'proxyid': _proxy_identifier 153 | } 154 | else: 155 | return { 156 | 'proxies': _getDomains(), 157 | 'path': "/%s/%s" % (kwargs['categ'], action), 158 | 'params': { 159 | }, 160 | 'proxyid': _proxy_identifier 161 | } 162 | 163 | def browse_build(data, action, page, **kwargs): 164 | '''browse_build are used to create a dict with the items when a movie or episode list is displayed. 165 | 166 | :param data: Contains a list with data from 'Request' 167 | :param action: (string) This parameter set an operation 168 | :param page: (int) Contains the current page number 169 | :param kwargs: (dict) Contain user parameters that were given to browse function 170 | :return: Return a dict 171 | ''' 172 | items = [] 173 | 174 | if kwargs['categ'] == 'movies': 175 | for movie in data: 176 | item = api_movies._create_item(movie) 177 | if item: 178 | items.append(item) 179 | if not items: 180 | return {} 181 | else: 182 | if kwargs['categ'] == 'movie_test': 183 | for movie in data: 184 | item = api_movies._create_item(movie) 185 | if item: 186 | items.append(item) 187 | if not items: 188 | return {} 189 | else: 190 | episodes = data['episodes'] 191 | for episode in episodes: 192 | episode2 = [] 193 | episode2.append(episode) 194 | episode2.append(kwargs) 195 | if kwargs['categ'] == 'show': 196 | item = api_tvShows._create_item(episode2) 197 | else: 198 | item = api_anime._create_item(episode2) 199 | if item: 200 | items.append(item) 201 | if not items: 202 | return {} 203 | items = sorted(items, key=lambda k: k['info']['episode']) 204 | return { 205 | 'pages': 50, #int(movie_count/settings.addon.limit) + (movie_count%settings.addon.limit > 0), # Specify the total number of pages (require) 206 | 'items': items 207 | } 208 | 209 | def search(query, page, **kwargs): 210 | '''This function is only used when we are calling search for movies''' 211 | _dom = _getDomains() 212 | _proxy = _proxy_identifier 213 | return api_movies._search(_proxy, _dom, query, page, **kwargs) 214 | 215 | def search_build(data, query, page, **kwargs): 216 | '''This function is only used when we are calling search for movies''' 217 | return api_movies._search_build(data, query, page, **kwargs) 218 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/apifetch/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/apifetch/__init__.py -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/apifetch/anime.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import sys 4 | import urllib2 5 | import xbmc 6 | import re 7 | 8 | from kodipopcorntime import settings 9 | from kodipopcorntime import favourites as _favs 10 | from kodipopcorntime.providers.movies import metadata_tmdb 11 | 12 | from .base import BaseContentWithSeasons 13 | 14 | 15 | __addon__ = sys.modules['__main__'].__addon__ 16 | 17 | _genres = { 18 | '30450': 'Action', 19 | '30451': 'Ecchi', 20 | '30452': 'Harem', 21 | '30453': 'Romance', 22 | '30454': 'School', 23 | '30455': 'Supernatural', 24 | '30456': 'Drama', 25 | '30457': 'Comedy', 26 | '30458': 'Mystery', 27 | '30459': 'Police', 28 | '30461': 'Sports', 29 | '30462': 'Mecha', 30 | '30463': 'Sci-Fi', 31 | '30464': 'Slice+of+Life', 32 | '30465': 'Fantasy', 33 | '30466': 'Adventure', 34 | '30467': 'Gore', 35 | '30468': 'Music', 36 | '30469': 'Psychological', 37 | '30470': 'Shoujo+Ai', 38 | '30471': 'Yuri', 39 | '30472': 'Magic', 40 | '30473': 'Horror', 41 | '30474': 'Thriller', 42 | '30475': 'Gender+Bender', 43 | '30476': 'Parody', 44 | '30477': 'Historical', 45 | '30478': 'Racing', 46 | '30479': 'Samurai', 47 | '30480': 'Super+Power', 48 | '30481': 'Military', 49 | '30482': 'Dementia', 50 | '30483': 'Mahou+Shounen', 51 | '30484': 'Game', 52 | '30485': 'Martial+Arts', 53 | '30486': 'Vampire', 54 | '30487': 'Kids', 55 | '30488': 'Mahou+Shoujo', 56 | '30489': 'Space', 57 | '30490': 'Shounen+Ai' 58 | } 59 | 60 | 61 | class Anime(BaseContentWithSeasons): 62 | action = 'anime' 63 | category = 'anime' 64 | # Request path is created as: '{domain}/{request_path}/kwargs[id_field]'. 65 | # We need to provide the correct values for request_path and id_field. 66 | id_field = '_id' 67 | request_path = 'anime' 68 | search_path = 'animes' 69 | 70 | @classmethod 71 | def _get_item_info(cls, data): 72 | tagline = '' 73 | try: 74 | tagline_temp = ('1080p: %s seeds; ' %data[0].get('torrents').get('1080p').get('seeds')) 75 | except: 76 | pass 77 | else: 78 | tagline += tagline_temp 79 | try: 80 | tagline_temp = ('720p: %s seeds; ' %data[0].get('torrents').get('720p').get('seeds')) 81 | except: 82 | pass 83 | else: 84 | tagline += tagline_temp 85 | try: 86 | tagline_temp = ('480p: %s seeds; ' %data[0].get('torrents').get('480p').get('seeds')) 87 | except: 88 | pass 89 | else: 90 | tagline += tagline_temp 91 | return { 92 | "mediatype": "episode", 93 | "title": data[0]['title'], 94 | "originaltitle": data[0]['title'], 95 | "tagline": tagline, 96 | "season": int(data[0].get('season') or 0), 97 | "episode": int(data[0].get('episode') or 0), 98 | "tvshowtitle": data[-1]['tvshow'], 99 | "duration": int(data[-1]['runtime'])*60, 100 | "status": data[-1]['status'], 101 | "genre": u" / ".join(genre for genre in data[0].get("genres", [])) or None, 102 | "code": data[0].get("tvdb_id"), 103 | "plot": data[0]['overview'], 104 | "plotoutline": data[0]['overview'] 105 | } 106 | 107 | 108 | def _folders(action, **kwargs): 109 | if action == 'cat_Anime': 110 | '''Action cat_TVShows creates a list of options for TV Shows ''' 111 | return [ 112 | { 113 | # Search Option 114 | "label": __addon__.getLocalizedString(30002), # "label" is require 115 | "icon": os.path.join(settings.addon.resources_path, 'media', 'movies', 'search.png'), 116 | "thumbnail": os.path.join(settings.addon.resources_path, 'media', 'movies', 'search.png'), 117 | "params": { 118 | "act": "search", 119 | 'search': 'true', 120 | 'action': 'anime-list', # Require when calling browse or folders (Action is used to separate the content) 121 | "endpoint": "folders", # "endpoint" is require 122 | 'page': 1, 123 | 'genre': 'all' 124 | } 125 | }, 126 | { 127 | # Best Rated Option 128 | "label": __addon__.getLocalizedString(30005), # "label" is require 129 | "icon": os.path.join(settings.addon.resources_path, 'media', 'movies', 'rated.png'), 130 | "thumbnail": os.path.join(settings.addon.resources_path, 'media', 'movies', 'rated.png'), 131 | "params": { 132 | "action": "anime-list", # Require when calling browse or folders (Action is used to separate the content) 133 | 'search': 'false', 134 | 'genre': 'all', 135 | "endpoint": "folders", # "endpoint" is require 136 | 'act': "rating", 137 | 'page': 1 138 | } 139 | }, 140 | { 141 | # Sort by Title Option 142 | "label": __addon__.getLocalizedString(30025), #Title # "label" is require 143 | "icon": os.path.join(settings.addon.resources_path, 'media', 'movies', 'rated.png'), 144 | "thumbnail": os.path.join(settings.addon.resources_path, 'media', 'movies', 'rated.png'), 145 | "params": { 146 | "action": "anime-list", # Require when calling browse or folders (Action is used to separate the content) 147 | 'search': 'false', 148 | 'genre': 'all', 149 | "endpoint": "folders", # "endpoint" is require 150 | 'act': "name", 151 | 'page': 1 152 | } 153 | }, 154 | { 155 | # Sort by Year Option 156 | "label": __addon__.getLocalizedString(30026), # "label" is require 157 | "icon": os.path.join(settings.addon.resources_path, 'media', 'movies', 'rated.png'), 158 | "thumbnail": os.path.join(settings.addon.resources_path, 'media', 'movies', 'rated.png'), 159 | "params": { 160 | "action": "anime-list", # Require when calling browse or folders (Action is used to separate the content) 161 | 'search': 'false', 162 | 'genre': 'all', 163 | "endpoint": "folders", # "endpoint" is require 164 | 'act': "year", 165 | 'page': 1 166 | } 167 | }, 168 | { 169 | # Browse by Genre Option 170 | "label": __addon__.getLocalizedString(30003), # "label" is require 171 | "icon": os.path.join(settings.addon.resources_path, 'media', 'movies', 'genres.png'), 172 | "thumbnail": os.path.join(settings.addon.resources_path, 'media', 'movies', 'genres.png'), 173 | "params": { 174 | "endpoint": "folders", # "endpoint" is require 175 | 'action': "genres_anime" # Require when calling browse or folders (Action is used to separate the content) 176 | } 177 | }, 178 | { 179 | # Favourites Option 180 | "label": __addon__.getLocalizedString(30029), # "label" is require 181 | "icon": os.path.join(settings.addon.resources_path, 'media', 'movies', 'rated.png'), 182 | "thumbnail": os.path.join(settings.addon.resources_path, 'media', 'movies', 'rated.png'), 183 | "params": { 184 | "action": "favorites_Anime", # Require when calling browse or folders (Action is used to separate the content) 185 | "endpoint": "folders", # "endpoint" is require 186 | } 187 | } 188 | ] 189 | 190 | if action == 'genres_anime': 191 | '''Action genres_anime creates a list of genres''' 192 | items= [] 193 | for n in __addon__.getLocalizedString(30498).split(','): 194 | if _genres.get(n): 195 | path = os.path.join(settings.addon.resources_path, 'media', 'movies', 'genres', '%s.png' %_genres[n]) 196 | items.append({ 197 | "label": __addon__.getLocalizedString(int(n)), # "label" is require 198 | "icon": path, 199 | "thumbnail": path, 200 | "params": { 201 | "action": "anime-list", # Require when calling browse or folders (Action is used to separate the content) 202 | 'search': 'false', 203 | "endpoint": "folders", # "endpoint" is require 204 | 'act': "genre", 205 | 'genre': _genres[n], 206 | 'page': 1 207 | } 208 | }) 209 | return items 210 | 211 | 212 | def _favourites(dom, **kwargs): 213 | 214 | action = 'anime' 215 | favs = _favs._get_favs(action) 216 | 217 | shows = [] 218 | for fa in favs: 219 | search = '%s/anime/%s' % (dom[0], fa['id']) 220 | req = urllib2.Request(search, headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", "Accept-Encoding": "none"}) 221 | response = urllib2.urlopen(req) 222 | show1 = json.loads(response.read()) 223 | 224 | shows.append({ 225 | "_id": fa['id'], 226 | "title": show1['title'], 227 | "year": show1['year'], 228 | "slug": show1['slug'], 229 | "images": show1['images'], 230 | "rating": show1['rating'] 231 | }) 232 | 233 | items = [] 234 | for show in shows: 235 | context_menu = [('%s' %__addon__.getLocalizedString(30040), 'RunPlugin(plugin://plugin.video.kodipopcorntime?cmd=remove_fav&action=%s&id=%s)' % (action, show['_id']))] 236 | context_menu = context_menu+[('%s' %__addon__.getLocalizedString(30043), 'RunPlugin(plugin://plugin.video.kodipopcorntime?cmd=remove_fav&action=%s&id=all)' %action)] 237 | items.append({ 238 | "label": show['title'], # "label" is require 239 | "icon": show.get('images').get('poster'), 240 | "thumbnail": show.get('images').get('poster'), 241 | "info": Anime.get_meta_info(show, 0), 242 | "properties": { 243 | "fanart_image": show.get('images').get('fanart'), 244 | }, 245 | "params": { 246 | "endpoint": "folders", # "endpoint" is require 247 | 'action': "anime-seasons", # Require when calling browse or folders (Action is used to separate the content) 248 | '_id': show['_id'], 249 | 'poster': show.get('images').get('poster'), 250 | 'fanart': show.get('images').get('fanart'), 251 | 'tvshow': show['title'] 252 | }, 253 | "context_menu": context_menu, 254 | "replace_context_menu": True 255 | }) 256 | 257 | return items 258 | 259 | 260 | def _shows(dom, **kwargs): 261 | return Anime.get_shows(dom, **kwargs) 262 | 263 | 264 | def _seasons(dom, **kwargs): 265 | return Anime.get_seasons(dom, **kwargs) 266 | 267 | 268 | def _create_item(data): 269 | return Anime._create_item(data) 270 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/apifetch/base.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import sys 4 | import urllib2 5 | 6 | import xbmc 7 | 8 | from kodipopcorntime import settings 9 | from kodipopcorntime.exceptions import Abort 10 | from kodipopcorntime.providers.movies import metadata_tmdb 11 | from kodipopcorntime.settings import addon as _settings 12 | 13 | 14 | __addon__ = sys.modules['__main__'].__addon__ 15 | 16 | 17 | class BaseContent(object): 18 | @classmethod 19 | def _create_item(cls, data): 20 | if not cls._is_item_valid_for_data(data): 21 | return {} 22 | 23 | torrents = cls._get_torrents_information(data) 24 | # Do not show content without torrents 25 | if not torrents: 26 | return {} 27 | 28 | return { 29 | 'label': cls._get_item_label(data), 30 | 'icon': cls._get_item_icon(data), 31 | 'thumbnail': cls._get_item_icon(data), 32 | 'info': cls._get_item_info(data), 33 | 'properties': cls._get_item_properties(data), 34 | 'stream_info': cls._get_item_stream_info(torrents), 35 | 'params': torrents, 36 | } 37 | 38 | @classmethod 39 | def _get_item_stream_info(cls, torrents): 40 | # Set video width and hight 41 | width = 640 42 | height = 480 43 | if torrents.get('1080p'): 44 | width = 1920 45 | height = 1080 46 | elif torrents.get('720p'): 47 | width = 1280 48 | height = 720 49 | 50 | return { 51 | 'video': { 52 | 'codec': u'h264', 53 | 'duration': int(0), 54 | 'width': width, 55 | 'height': height, 56 | }, 57 | 'audio': { 58 | 'codec': u'aac', 59 | 'language': u'en', 60 | 'channels': 2, 61 | }, 62 | } 63 | 64 | 65 | class BaseContentWithSeasons(BaseContent): 66 | @classmethod 67 | def get_meta_info(a, result, season): 68 | info = { 69 | 'mediatype': 'tvshow', 70 | 'title': result['title'], 71 | 'originaltitle': result['title'], 72 | 'year': int(result['year']), 73 | 'rating': float(int(result.get('rating').get('percentage'))/10), 74 | 'votes': result.get('rating').get('votes'), 75 | 'code': result['_id'], 76 | 'imdbnumber': result['_id'], 77 | } 78 | if season == 0 and result['_id'].startswith('tt'): 79 | try: 80 | meta = metadata_tmdb._get_info(result['_id'], 0) 81 | castandrole = [] 82 | for c in meta['credits'].get("cast", []): 83 | castandrole.append((c["name"], c.get("character", ''))) 84 | info = { 85 | 'mediatype': 'tvshow', 86 | 'title': meta['name'], 87 | 'originaltitle': meta['original_name'], 88 | 'year': int(result['year']), 89 | 'rating': float(meta['vote_average']), 90 | 'votes': meta['vote_count'], 91 | 'status': meta['status'], 92 | 'country': meta['origin_country'][0], 93 | 'code': result['_id'], 94 | 'imdbnumber': result['_id'], 95 | 'plot': meta['overview'], 96 | 'plotoutline': meta['overview'], 97 | 'castandrole': castandrole, 98 | } 99 | except: 100 | pass 101 | if not season == 0 and result['_id'].startswith('tt'): 102 | try: 103 | meta = metadata_tmdb._get_info(result['_id'], season) 104 | castandrole = [] 105 | for c in meta['credits'].get("cast", []): 106 | castandrole.append((c["name"], c.get("character", ''))) 107 | info = { 108 | "mediatype": "season", 109 | "title": result['title'], 110 | "tvshowtitle": result['title'], 111 | 'season': season, 112 | 'status': result['status'], 113 | 'code': result['_id'], 114 | 'imdbnumber': result['_id'], 115 | "plotoutline": meta['overview'] or None, 116 | "plot": meta['overview'] or None, 117 | 'castandrole': castandrole, 118 | } 119 | except: 120 | info = { 121 | "mediatype": "season", 122 | "title": result['title'], 123 | "tvshowtitle": result['title'], 124 | "plotoutline": result['synopsis'] or None, 125 | "plot": result['synopsis'] or None 126 | } 127 | else: 128 | try: 129 | meta = metadata_tmdb._get_anime_info(result['_id']) 130 | info = { 131 | 'mediatype': 'tvshow', 132 | 'title': meta['attributes']['titles']['en'], 133 | 'originaltitle': meta['attributes']['titles']['ja_jp'], 134 | 'year': int(result['year']), 135 | 'rating': float(int(result.get('rating').get('percentage'))/10), 136 | 'votes': result.get('rating').get('votes'), 137 | 'code': result['_id'], 138 | 'plot': meta['attributes']['synopsis'], 139 | 'plotoutline': meta['attributes']['synopsis'], 140 | } 141 | except: 142 | pass 143 | return info 144 | 145 | @classmethod 146 | def get_shows(cls, dom, **kwargs): 147 | if kwargs['search'] == 'true': 148 | search_string = xbmc.getInfoLabel("ListItem.Property(searchString)") 149 | if not search_string: 150 | keyboard = xbmc.Keyboard('', __addon__.getLocalizedString(30001), False) 151 | keyboard.doModal() 152 | if not keyboard.isConfirmed() or not keyboard.getText(): 153 | raise Abort() 154 | search_string = keyboard.getText() 155 | search_string = search_string.replace(' ', '+') 156 | search = '{domain}/{search_path}/1?keywords={keywords}'.format( 157 | domain=dom[0], 158 | search_path=cls.search_path, 159 | keywords=search_string, 160 | ) 161 | else: 162 | search = '{domain}/{search_path}/{page}?genre={genre}&sort={sort}'.format( 163 | domain=dom[0], 164 | search_path=cls.search_path, 165 | page=kwargs['page'], 166 | genre=kwargs['genre'], 167 | sort=kwargs['act'], 168 | ) 169 | 170 | req = urllib2.Request( 171 | search, 172 | headers={ 173 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", 174 | "Accept-Encoding": "none", 175 | }, 176 | ) 177 | response = urllib2.urlopen(req) 178 | results = json.loads(response.read()) 179 | 180 | items = [ 181 | { 182 | "label": result['title'], # "label" is required 183 | "icon": result.get('images').get('poster'), 184 | "thumbnail": result.get('images').get('poster'), 185 | "info": cls.get_meta_info(result, 0), 186 | "properties": { 187 | "fanart_image": result.get('images').get('fanart'), 188 | }, 189 | "params": { 190 | "endpoint": "folders", # "endpoint" is required 191 | 'action': "{category}-seasons".format(category=cls.category), # Required when calling browse or folders (Action is used to separate the content) 192 | cls.id_field: result[cls.id_field], 193 | 'poster': result.get('images').get('poster'), 194 | 'fanart': result.get('images').get('fanart'), 195 | 'tvshow': result['title'] 196 | }, 197 | "context_menu": [ 198 | ( 199 | '%s' % __addon__.getLocalizedString(30039), 200 | 'RunPlugin(plugin://plugin.video.kodipopcorntime?cmd=add_fav&action={action}&id={id})'.format( 201 | action=cls.action, 202 | id=result[cls.id_field], 203 | ), 204 | ) 205 | ], 206 | "replace_context_menu": True 207 | } 208 | for result in results 209 | ] 210 | 211 | # Next Page 212 | items.append({ 213 | "label": 'Show more', # "label" is required 214 | "icon": os.path.join(settings.addon.resources_path, 'media', 'movies', 'more.png'), 215 | "thumbnail": os.path.join(settings.addon.resources_path, 'media', 'movies', 'more_thumbnail.png'), 216 | "params": { 217 | "endpoint": "folders", # "endpoint" is required 218 | 'action': "{category}-list".format(category=cls.category), # Required when calling browse or folders (Action is used to separate the content) 219 | 'act': kwargs['act'], 220 | 'genre': kwargs['genre'], 221 | 'search': kwargs['search'], 222 | 'page': int(kwargs['page']) + 1, 223 | }, 224 | }) 225 | 226 | return items 227 | 228 | @classmethod 229 | def get_seasons(cls, dom, **kwargs): 230 | req = urllib2.Request( 231 | '{domain}/{request_path}/{content_id}'.format( 232 | domain=dom[0], 233 | request_path=cls.request_path, 234 | content_id=kwargs[cls.id_field] 235 | ), 236 | headers={ 237 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", 238 | "Accept-Encoding": "none", 239 | }, 240 | ) 241 | 242 | response = urllib2.urlopen(req) 243 | result = json.loads(response.read()) 244 | seasons = result['episodes'] 245 | 246 | season_list = sorted(list(set( 247 | season['season'] 248 | for season in seasons 249 | ))) 250 | 251 | try: 252 | result['country'] 253 | except: 254 | country = None 255 | else: 256 | country = result['country'] 257 | 258 | return [ 259 | { 260 | "label": 'Season %s' % season, # "label" is required 261 | "icon": kwargs['poster'], 262 | "thumbnail": kwargs['poster'], 263 | "info": cls.get_meta_info(result, season), 264 | "properties": { 265 | "fanart_image": kwargs['fanart'] 266 | }, 267 | "params": { 268 | 'categ': cls.category, # "categ" is required when using browse as an endpoint 269 | 'seasons': season, 270 | 'image': kwargs['poster'], 271 | 'image2': kwargs['fanart'], 272 | 'tvshow': kwargs['tvshow'], 273 | "status": result['status'], 274 | "runtime": result['runtime'], 275 | "country": country, 276 | "endpoint": "browse", # "endpoint" is required 277 | 'action': kwargs[cls.id_field] # Required when calling browse or folders (Action is used to separate the content) 278 | } 279 | } 280 | for season in season_list 281 | ] 282 | 283 | @staticmethod 284 | def _is_item_valid_for_data(data): 285 | # seasondata0 has all the data from show 286 | seasondata0 = int(data[0]['season']) 287 | # seasondata_1 carries additional user data not included in show data 288 | seasondata_1 = int(data[-1]['seasons']) 289 | 290 | return (seasondata0 == seasondata_1) 291 | 292 | @staticmethod 293 | def _get_item_icon(data): 294 | return data[-1]['image'] 295 | 296 | @staticmethod 297 | def _get_item_label(data): 298 | return 'Episode {number}: {title}'.format( 299 | number=data[0]['episode'], 300 | title=data[0]['title'], 301 | ) 302 | 303 | @staticmethod 304 | def _get_item_properties(data): 305 | return { 306 | 'fanart_image': data[-1]['image2'], 307 | 'tvshowthumb': data[-1]['image2'], 308 | } 309 | 310 | @staticmethod 311 | def _get_torrents_information(data): 312 | torrents = {} 313 | for quality, torrent_info in data[0].get('torrents', {}).items(): 314 | torrent_url = torrent_info.get('url') 315 | if quality in settings.QUALITIES and torrent_url is not None: 316 | torrents.update({ 317 | quality: torrent_url, 318 | '{0}size'.format(quality): 1000000000*60, 319 | }) 320 | return torrents 321 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/movies/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/movies/metadata_tmdb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import time, json, urllib2, urllib 3 | from kodipopcorntime.utils import Cache 4 | from kodipopcorntime.logging import log, LOGLEVEL 5 | 6 | FALLBACKLANG = 'en' # Or None 7 | 8 | _api_key = "308a68c313ff66d165c1eb029b0716bc" 9 | _base_url = "http://api.themoviedb.org" 10 | _anime_base_url = "https://kitsu.io/api/edge" 11 | _images_base_url = None 12 | 13 | class _Data(): 14 | _limit = 40 15 | _timelimit = 10 16 | _count = 0 17 | _time = 0 18 | imageUrl = "http://image.tmdb.org/t/p" 19 | 20 | @staticmethod 21 | def limit(): 22 | if not _Data._time: 23 | _Data._time = time.time() 24 | 25 | _timediff = time.time()-_Data._time 26 | if _Data._count >= _Data._limit: 27 | while _timediff < _Data._timelimit: 28 | print('sleep') 29 | time.sleep(_timediff) 30 | _timediff = time.time()-_Data._time 31 | if _timediff >= _Data._timelimit: 32 | _Data._count = 0 33 | _Data._time = time.time() 34 | 35 | _Data._count = _Data._count+1 36 | 37 | def _credits(credits): 38 | log("(tmdb-credits) %s" %credits, LOGLEVEL.INFO) 39 | castandrole = [] 40 | director = [] 41 | writer = [] 42 | 43 | for c in credits.get("cast", []): 44 | castandrole.append((c["name"], c.get("character", ''))) 45 | 46 | for c in credits.get("crew", []): 47 | if c["job"] == 'Director': 48 | director.append(c["name"]) 49 | continue 50 | if c["job"] in ('Novel', 'Screenplay', 'Writer'): 51 | writer.append(c["name"]) 52 | 53 | return { 54 | 'castandrole': castandrole, 55 | 'director': u" / ".join(director), 56 | 'writer': u" / ".join(writer) 57 | } 58 | 59 | def _info(meta, title=''): 60 | log("(tmdb-info) %s; %s" % (meta, title), LOGLEVEL.INFO) 61 | try: 62 | if meta['data']: 63 | log("(tmdb-info-except) %s" %meta['data'][0]['attributes']['canonicalTitle'], LOGLEVEL.INFO) 64 | if not meta['data'][0]['attributes']['canonicalTitle'] == 'None': 65 | title = meta['data'][0]['attributes']['canonicalTitle'] 66 | item = { 67 | "title": title, 68 | "year": int(meta['data'][0]['attributes']['airdate'].split("-").pop(0)), 69 | "originaltitle": title, 70 | "plot": meta['data'][0]['attributes']['synopsis'], 71 | "plotoutline": meta['data'][0]['attributes']['synopsis'], 72 | "code": meta['data'][0]['id'], 73 | } 74 | except: 75 | overview = meta.get('overview', '') 76 | vote_average = meta.get('vote_average') 77 | item = { 78 | "title": title, 79 | "year": int(meta.get("release_date", '0').split("-").pop(0)), 80 | "originaltitle": meta.get("original_title", ''), 81 | "genre": u" / ".join(g["name"] for g in meta.get("genres", [])), 82 | "plot": overview, 83 | "plotoutline": overview, 84 | "tagline": meta.get("tagline", ''), 85 | "rating": float(vote_average or 0.0), 86 | "duration": int(meta.get("runtime") or 0), 87 | "code": meta.get("imdb_id"), 88 | "studio": u" / ".join([s['name'] for s in meta.get("production_companies", [])]), 89 | "votes": vote_average and float(meta.get("vote_count")) or 0.0 90 | } 91 | credits = meta.get("credits") 92 | if credits: 93 | item.update(_credits(credits)) 94 | 95 | return item 96 | 97 | def _stream_info(meta): 98 | log("(tmdb-stream-info) %s" %meta, LOGLEVEL.INFO) 99 | return { 100 | "video": { 101 | "duration": int((meta.get("runtime") or 0)*60) 102 | } 103 | } 104 | 105 | def _properties(meta): 106 | log("(tmdb-properties) %s" %meta, LOGLEVEL.INFO) 107 | fanart = meta.get("fanart", '') 108 | if _Data.imageUrl and fanart: 109 | fanart = "%s/w500%s" %(_Data.imageUrl, fanart) 110 | 111 | return {"fanart_image": fanart} 112 | 113 | def pre(): 114 | cache = Cache("movies.metadata.imdb.conf", ttl=24 * 3600, readOnly=True) 115 | try: 116 | _Data.imageUrl = cache["imageUrl"] 117 | except KeyError: 118 | _Data.limit() 119 | return [{ 120 | 'domain': _base_url, 121 | 'path': "/3/configuration", 122 | 'params': { 123 | "api_key": _api_key 124 | } 125 | }] 126 | return [] 127 | 128 | def build_pre(data): 129 | log("(tmdb-build_pre) %s" %data, LOGLEVEL.INFO) 130 | if data: 131 | _Data.imageUrl = data[0].get("images", {}).get("base_url") 132 | if _Data.imageUrl: 133 | Cache("movies.metadata.imdb.conf", ttl=24 * 3600)['imageUrl'] = _Data.imageUrl 134 | 135 | def item(id, label, year, lang): 136 | log("(tmdb-item) %s; %s; %s; %s" % (id, label, year, lang), LOGLEVEL.INFO) 137 | _Data.limit() 138 | time.sleep(50.0 / 1000.0) 139 | if label.startswith('Episode'): 140 | try: 141 | url = '%s/3/find/%s?api_key=%s&external_source=tvdb_id' % (_base_url, id, _api_key) 142 | req = urllib2.Request(url, headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", "Accept-Encoding": "none"}) 143 | response = urllib2.urlopen(req) 144 | result = json.loads(response.read()) 145 | metadat = result['tv_episode_results'] 146 | return { 147 | 'domain': _base_url, 148 | 'path': "/3/tv/%s/season/%s/episode/%s" % (metadat[0]['show_id'], metadat[0]['season_number'], metadat[0]['episode_number']), 149 | 'params': { 150 | "api_key": _api_key, 151 | "append_to_response": "credits", 152 | "language": lang, 153 | "include_image_language": "en,null" 154 | } 155 | } 156 | except: 157 | episode = int(label[-1:])-1 158 | id = id.split('-')[0] 159 | path = '/anime/%s/episodes?page limit=1&page offset=%s&sort=number' % (id, episode) 160 | path = path.replace(' ', '%5B') 161 | return { 162 | 'domain': _anime_base_url, 163 | 'path': path, 164 | 'params': { 165 | } 166 | } 167 | else: 168 | return { 169 | 'domain': _base_url, 170 | 'path': "/3/movie/%s" %id, 171 | 'params': { 172 | "api_key": _api_key, 173 | "append_to_response": "credits", 174 | "language": lang, 175 | "include_image_language": "en,null" 176 | } 177 | } 178 | 179 | def build_item(meta, id, label, year, lang): 180 | log("(tmdb-build_item) %s; %s; %s; %s; %s" % (meta, id, label, year, lang), LOGLEVEL.INFO) 181 | if not meta or meta.get('status_code'): 182 | return {} 183 | 184 | title = meta.get("title", '') 185 | 186 | poster = meta.get("poster_path", '') 187 | if _Data.imageUrl and poster: 188 | poster = "%s/w185%s" %(_Data.imageUrl, poster) 189 | 190 | item = { 191 | "label": title, 192 | "icon": poster, 193 | "thumbnail": poster 194 | } 195 | item.setdefault('info', {}).update(_info(meta, title)) 196 | item.setdefault('stream_info', {}).update(_stream_info(meta)) 197 | item.setdefault('properties', {}).update(_properties(meta)) 198 | 199 | log("(tmdb-return) %s" %item, LOGLEVEL.INFO) 200 | return item 201 | 202 | def _get_info(id, season): 203 | url = '%s/3/find/%s?api_key=%s&external_source=imdb_id' % (_base_url, id, _api_key) 204 | req = urllib2.Request(url, headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", "Accept-Encoding": "none"}) 205 | response = urllib2.urlopen(req) 206 | result = json.loads(response.read()) 207 | metadat = result['tv_results'] 208 | 209 | if season == 0: 210 | url2 = '%s/3/tv/%s?api_key=%s&append_to_response=credits&include_image_language=en,null' % (_base_url, metadat[0]['id'], _api_key) 211 | else: 212 | url2 = '%s/3/tv/%s/season/%s?api_key=%s&append_to_response=credits&include_image_language=en,null' % (_base_url, metadat[0]['id'],season, _api_key) 213 | req2 = urllib2.Request(url2, headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", "Accept-Encoding": "none"}) 214 | response2 = urllib2.urlopen(req2) 215 | result2 = json.loads(response2.read()) 216 | return result2 217 | 218 | def _get_anime_info(id): 219 | url = '%s/anime/%s' % (_anime_base_url, id) 220 | req = urllib2.Request(url, headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", "Accept-Encoding": "none"}) 221 | response = urllib2.urlopen(req) 222 | result = json.loads(response.read()) 223 | return result['data'] 224 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/providers/movies/subtitle_yify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from kodipopcorntime.settings import movies as _settings 3 | 4 | _subtitleISO2Name = { 5 | "sq": "albanian", 6 | "ar": "arabic", 7 | "bn": "bengali", 8 | "pt-br": "brazilian-portuguese", 9 | "bg": "bulgarian", 10 | "zh": "chinese", 11 | "hr": "croatian", 12 | "cs": "czech", 13 | "da": "danish", 14 | "nl": "dutch", 15 | "en": "english", 16 | "fa": "farsi-persian", 17 | "fi": "finnish", 18 | "fr": "french", 19 | "de": "german", 20 | "el": "greek", 21 | "he": "hebrew", 22 | "hu": "hungarian", 23 | "id": "indonesian", 24 | "it": "italian", 25 | "ja": "japanese", 26 | "ko": "korean", 27 | "lt": "lithuanian", 28 | "mk": "macedonian", 29 | "ms": "malay", 30 | "no": "norwegian", 31 | "pl": "polish", 32 | "pt": "portuguese", 33 | "ro": "romanian", 34 | "ru": "russian", 35 | "sr": "serbian", 36 | "sl": "slovenian", 37 | "es": "spanish", 38 | "sv": "swedish", 39 | "th": "thai", 40 | "tr": "turkish", 41 | "ur": "urdu", 42 | "vi": "vietnamese" 43 | } 44 | 45 | def pre(): 46 | pass 47 | 48 | def build_pre(data): 49 | pass 50 | 51 | def item(id, label, year): 52 | return { 53 | 'domain': 'https://api.yts-subs.com', 54 | 'path': "/movie-imdb/%s" %id 55 | } 56 | 57 | def build_item(data, id, label, year): 58 | data = data.get("subs", {}).get(id, None) 59 | if not data: 60 | return {} 61 | 62 | for lang in _settings.preferred_subtitles: 63 | subtitlelanguage = _subtitleISO2Name.get(lang, None) 64 | if not subtitlelanguage: 65 | continue 66 | subtitles = data.get(subtitlelanguage, []) 67 | if not subtitles: 68 | continue 69 | 70 | # Sort the subtitles 71 | # Option a: Find highest rated subtitle. 72 | # If there is not a subtitle used highest rated hearing impaired subtitles. 73 | # Option b: Find highest rated hearing impaired subtitle. 74 | # If there is not a hearing impaired subtitle used highest rated subtitle. 75 | subtitle = subtitles.pop(0) 76 | for s in subtitles: 77 | hi = s["hi"] 78 | if _settings.prioritere_impaired: 79 | hi = -(s["hi"]-2) 80 | if s["rating"] <= subtitle["rating"] and hi >= subtitle["hi"] or hi > subtitle["hi"]: 81 | continue 82 | subtitle = s 83 | 84 | return { 85 | 'stream_info': { 86 | 'subtitle': { 87 | 'language': lang 88 | } 89 | }, 90 | "params": { 91 | "subtitle": 'https://api.yts-subs.com%s' %subtitle["url"] 92 | } 93 | } 94 | 95 | return {} 96 | 97 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/request.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import zlib, simplejson, sys, socket, httplib, errno, xbmcaddon, xbmc, os, json 3 | from urlparse import urlparse 4 | from urllib import urlencode 5 | from kodipopcorntime.utils import Cache 6 | from kodipopcorntime.exceptions import HTTPError, ProxyError 7 | from kodipopcorntime.logging import log, LOGLEVEL 8 | from kodipopcorntime.settings import addon as _settings 9 | from kodipopcorntime import favourites as _favs 10 | 11 | __addon__ = xbmcaddon.Addon() 12 | __addonname__ = __addon__.getAddonInfo('name') 13 | __addondir__ = xbmc.translatePath(__addon__.getAddonInfo('profile')) 14 | 15 | _json_file = os.path.join(__addondir__, 'test.json') 16 | 17 | class URL(object): 18 | def __init__(self): 19 | self.conn = self.scheme = self.netloc = self.uri = self.url = None 20 | # Default headers 21 | self.headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36", "Accept-Encoding": "gzip"} 22 | 23 | def __enter__(self): 24 | return self 25 | 26 | def request_proxy(self, proxies, path, proxyid, params={}, headers={}, timeout=10): 27 | log("(URL) Proxy domain is activated", LOGLEVEL.NONE) 28 | with Cache(proxyid) as proxies_cache: 29 | if not proxies_cache or not all(p in proxies_cache['proxies'] for p in proxies): 30 | proxies_cache['proxies'] = proxies 31 | 32 | for proxy in proxies_cache['proxies'][:]: 33 | try: 34 | if path == 'movie_favs': 35 | _favs._create_movie_favs() 36 | with open(_json_file) as json_read: 37 | _data = json.load(json_read) 38 | else: 39 | _data = self.request(proxy, path, params, headers, timeout) 40 | if _data or _data is None: 41 | return _data 42 | except (HTTPError, socket.timeout, socket.gaierror, socket.herror, socket.error) as e: 43 | if e.__class__.__name__ == 'error': 44 | if not e.errno in [errno.EUSERS, errno.ECONNRESET, errno.ETIMEDOUT, errno.ECONNREFUSED, errno.EHOSTDOWN]: 45 | raise 46 | log("(URL) %s: %s - %s" %(e.__class__.__name__, str(e), self.url), LOGLEVEL.ERROR) 47 | sys.exc_clear() 48 | log("(URL) Proxy domain '%s' is not working and will therefore have low priority in the future" %proxy, LOGLEVEL.NOTICE) 49 | proxies_cache.extendKey('proxies', [proxies_cache['proxies'].pop(0)]) 50 | raise ProxyError("There was not any domains that worked", 30328) 51 | 52 | def request(self, domain, path, params={}, headers={}, timeout=10): 53 | headers.update(self.headers) 54 | log("(URL) Headers: %s" %str(headers), LOGLEVEL.NONE) 55 | 56 | self.scheme, self.netloc, self.uri, self.url = self.urlParse(domain, path, params) 57 | log("(URL) Trying to obtaining data from %s" %self.url, LOGLEVEL.NONE) 58 | try: 59 | if self.scheme == 'https': 60 | self.conn = httplib.HTTPSConnection(self.netloc, timeout=timeout) 61 | else: 62 | self.conn = httplib.HTTPConnection(self.netloc, timeout=timeout) 63 | if _settings.debug: 64 | self.conn.set_debuglevel(1) 65 | self.conn.request("GET", self.uri, headers=headers) 66 | response = self.conn.getresponse() 67 | if response.status == httplib.OK: 68 | return self._read(response) 69 | raise HTTPError("Received wrong status code (%s %s) from %s" %(response.status, httplib.responses.get(response.status, ''), self.url), 30329) 70 | except socket.error as e: 71 | if e.errno in [errno.ESHUTDOWN, errno.ECONNABORTED]: 72 | log("(URL) socket.error: %s. (Connection has most likely been canceled by user choices)" %str(e), LOGLEVEL.NOTICE) 73 | sys.exc_clear() 74 | return None 75 | raise 76 | 77 | def decompress(self, data): 78 | log("(Request) Decompress content", LOGLEVEL.NONE) 79 | return zlib.decompressobj(16 + zlib.MAX_WBITS).decompress(data) 80 | 81 | def urlParse(self, domain, path, params={}): 82 | scheme, netloc, _path, _, _, _ = urlparse(domain) 83 | if _path and not _path == '/': # Support path in domain 84 | if _path[-1:] == '/': 85 | path = _path[:-1]+path 86 | else: 87 | path = _path+path 88 | uri = path 89 | if params: 90 | uri = "%s?%s" %(path, urlencode(params)) 91 | return scheme, netloc, uri, "%s://%s%s" %(scheme, netloc, uri) 92 | 93 | def _read(self, response): 94 | log("(URL) Reading response", LOGLEVEL.NONE) 95 | data = response.read() 96 | if data and response.getheader("Content-Encoding", "") == "gzip": 97 | data = self.decompress(data) 98 | if not data: 99 | log("(URL) Did not receive any data %s" %self.url, LOGLEVEL.NOTICE) 100 | else: 101 | log("(URL) Successful receive data from %s" %self.url, LOGLEVEL.NONE) 102 | return data 103 | 104 | def __exit__(self, *exc_info): 105 | self.close() 106 | return not exc_info[0] 107 | 108 | def __del__(self): 109 | self.close() 110 | 111 | def cancel(self): 112 | self.close() 113 | 114 | def close(self): 115 | if hasattr(self, 'conn'): 116 | if self.conn and self.conn.sock: 117 | self.conn.sock.shutdown(socket.SHUT_RDWR) 118 | self.conn = None 119 | 120 | class Send(URL): 121 | def request_proxy(self, *agrs, **kwagrs): 122 | return super(Send, self).request_proxy(*agrs, **kwagrs) 123 | 124 | def request(self, *agrs, **kwagrs): 125 | return super(Send, self).request(*agrs, **kwagrs) 126 | 127 | def _read(self, response): 128 | return None 129 | 130 | class Json(URL): 131 | def _read(self, response): 132 | log("(URL) Reading response", LOGLEVEL.NONE) 133 | data = response.read() 134 | if data and response.getheader("Content-Encoding", "") == "gzip": 135 | data = self.decompress(data) 136 | if not data: 137 | log("(URL) Did not receive any data %s" %self.url, LOGLEVEL.NOTICE) 138 | else: 139 | log("(Json) Reading JSON data", LOGLEVEL.NONE) 140 | data = simplejson.loads(data, 'UTF-8') 141 | if data: 142 | log("(Json) Successful receive data from %s" %self.url, LOGLEVEL.NONE) 143 | return data 144 | 145 | class Download(URL): 146 | def __init__(self): 147 | # Default headers 148 | self.headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.66 Safari/537.36"} 149 | self.downloadPath = None 150 | 151 | def request_proxy(self, downloadPath, *agrs, **kwagrs): 152 | self.downloadPath = downloadPath 153 | return super(Download, self).request_proxy(*agrs, **kwagrs) 154 | 155 | def request(self, downloadPath, *agrs, **kwagrs): 156 | self.downloadPath = downloadPath 157 | return super(Download, self).request(*agrs, **kwagrs) 158 | 159 | def _read(self, response): 160 | log("(Download) Store data on location %s" %self.downloadPath, LOGLEVEL.NONE) 161 | with open(self.downloadPath, "wb") as f: 162 | f.write(response.read()) 163 | log("(Download) Successful stored data at %s" %self.url, LOGLEVEL.NONE) 164 | return self.downloadPath 165 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/settings/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | BUILD = 2 4 | 5 | MEDIATYPES = [ 6 | 'movies' 7 | #'tvshows' 8 | ] 9 | 10 | SUBTITLE_ISO = [ 11 | "sq", 12 | "ar", 13 | "bn", 14 | "pt-br", 15 | "bg", 16 | "zh", 17 | "hr", 18 | "cs", 19 | "da", 20 | "nl", 21 | "en", 22 | "fa", 23 | "fi", 24 | "fr", 25 | "de", 26 | "el", 27 | "he", 28 | "hu", 29 | "id", 30 | "it", 31 | "ja", 32 | "ko", 33 | "lt", 34 | "mk", 35 | "ms", 36 | "no", 37 | "pl", 38 | "pt", 39 | "ro", 40 | "ru", 41 | "sr", 42 | "sl", 43 | "es", 44 | "sv", 45 | "th", 46 | "tr", 47 | "ur", 48 | "vi" 49 | ] 50 | 51 | ISOTRANSLATEINDEX = { 52 | "sq": 30201, 53 | "ar": 30202, 54 | "bn": 30203, 55 | "pt-br": 30204, 56 | "bg": 30205, 57 | "zh": 30206, 58 | "hr": 30207, 59 | "cs": 30208, 60 | "da": 30209, 61 | "nl": 30210, 62 | "en": 30211, 63 | "fa": 30212, 64 | "fi": 30213, 65 | "fr": 30214, 66 | "de": 30215, 67 | "el": 30216, 68 | "he": 30217, 69 | "hu": 30218, 70 | "id": 30219, 71 | "it": 30220, 72 | "ja": 30221, 73 | "ko": 30222, 74 | "lt": 30223, 75 | "mk": 30224, 76 | "ms": 30225, 77 | "no": 30226, 78 | "pl": 30227, 79 | "pt": 30228, 80 | "ro": 30229, 81 | "ru": 30230, 82 | "sr": 30231, 83 | "sl": 30232, 84 | "es": 30233, 85 | "sv": 30234, 86 | "th": 30235, 87 | "tr": 30236, 88 | "ur": 30237, 89 | "vi": 30238 90 | } 91 | 92 | PUBLIC_TRACKERS = [ 93 | "udp://tracker.openbittorrent.com:80/announce", 94 | "udp://open.demonii.com:1337/announce", 95 | 'udp://tracker.leechers-paradise.org:6969/announce', 96 | "udp://tracker.istole.it:80/announce", 97 | "udp://tracker.coppersurfer.tk:6969/announce", 98 | "udp://tracker.publicbt.com:80/announce", 99 | "udp://exodus.desync.com:6969/announce", 100 | "udp://exodus.desync.com:80/announce", 101 | "udp://tracker.yify-torrents.com:80/announce", 102 | 'http://tracker.openbittorrent.kg:2710/announce', 103 | 'http://tracker.leechers-paradise.org:6969/announce', 104 | "http://tracker.istole.it:80/announce", 105 | "http://tracker.coppersurfer.tk:6969/announce", 106 | "http://tracker.publicbt.com:80/announce", 107 | "http://exodus.desync.com:6969/announce", 108 | "http://exodus.desync.com:80/announce", 109 | "http://tracker.yify-torrents.com:80/announce" 110 | ] 111 | 112 | QUALITIES = [ 113 | '3D', 114 | '1080p', 115 | '720p', 116 | '480p' 117 | ] 118 | 119 | from .addon import Addon as addon 120 | from .movies import Movies as movies 121 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/settings/addon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import sys 4 | import xbmc 5 | from datetime import date 6 | from kodipopcorntime.exceptions import Error 7 | from kodipopcorntime.logging import log, LOGLEVEL 8 | from .base import _Base, _MetaClass 9 | 10 | __addon__ = sys.modules['__main__'].__addon__ 11 | 12 | SPECIAL_DATES = ( 13 | { 14 | 'name': 'christmass', 15 | 'image': 'xmass.jpg', 16 | 'start': {'month': 12, 'day': 22}, 17 | 'end': {'month': 12, 'day': 27}, 18 | }, 19 | { 20 | 'name': 'new year', 21 | 'image': 'new_year.jpg', 22 | 'start': {'month': 12, 'day': 29}, 23 | 'end': {'month': 12, 'day': 31}, 24 | }, 25 | { 26 | 'name': 'new year 2', 27 | 'image': 'new_year.jpg', 28 | 'start': {'month': 1, 'day': 1}, 29 | 'end': {'month': 1, 'day': 5}, 30 | }, 31 | { 32 | 'name': 'valentine', 33 | 'image': 'valentine.jpg', 34 | 'start': {'month': 2, 'day': 13}, 35 | 'end': {'month': 2, 'day': 15}, 36 | }, 37 | { 38 | 'name': 'halloween', 39 | 'image': 'haloween.jpg', 40 | 'start': {'month': 10, 'day': 28}, 41 | 'end': {'month': 10, 'day': 31}, 42 | }, 43 | ) 44 | 45 | 46 | class Addon(_Base): 47 | class __metaclass__(_MetaClass): 48 | def _base_url(cls): 49 | cls.base_url = sys.argv[0] 50 | 51 | def _handle(cls): 52 | cls.handle = int(sys.argv[1]) 53 | 54 | def _cur_uri(cls): 55 | cls.cur_uri = sys.argv[2][1:] 56 | 57 | def _language(cls): 58 | cls.language = xbmc.getLanguage(xbmc.ISO_639_1) 59 | 60 | def _cache_path(cls): 61 | _path = xbmc.translatePath("special://profile/addon_data/%s/cache" % cls.id) 62 | if not os.path.exists(_path): 63 | os.makedirs(_path) 64 | if not os.path.exists(_path): 65 | raise Error("Unable to create cache directory %s" % _path, 30322) 66 | cls.cache_path = _path.encode(cls.fsencoding) 67 | 68 | def _resources_path(cls): 69 | cls.resources_path = os.path.join(__addon__.getAddonInfo('path'), 'resources') 70 | 71 | def _debug(cls): 72 | cls.debug = __addon__.getSetting("debug") == 'true' 73 | 74 | def _id(cls): 75 | cls.id = __addon__.getAddonInfo('id') 76 | 77 | def _name(cls): 78 | cls.name = __addon__.getAddonInfo('name') 79 | 80 | def _version(cls): 81 | cls.version = __addon__.getAddonInfo('version') 82 | 83 | def _fanart(cls): 84 | today = date.today() 85 | 86 | # Check special dates 87 | for special_date in SPECIAL_DATES: 88 | start_date = date(today.year, special_date['start']['month'], special_date['start']['day']) 89 | end_date = date(today.year, special_date['end']['month'], special_date['end']['day']) 90 | 91 | if start_date <= today <= end_date: 92 | log( 93 | '(settings-date) {0} {1}'.format(special_date['name'], today), 94 | LOGLEVEL.INFO, 95 | ) 96 | cls.fanart = os.path.join( 97 | __addon__.getAddonInfo('path'), 98 | 'resources', 99 | 'media', 100 | 'background', 101 | special_date['image'], 102 | ) 103 | break 104 | # No special date 105 | else: 106 | log('(settings-date) no condition met {0}'.format(today), LOGLEVEL.INFO) 107 | cls.fanart = __addon__.getAddonInfo('fanart') 108 | 109 | def _info_image(cls): 110 | cls.info_image = os.path.join(__addon__.getAddonInfo('path'), 'resources', 'media', 'info.png') 111 | 112 | def _warning_image(cls): 113 | cls.warning_image = os.path.join(__addon__.getAddonInfo('path'), 'resources', 'media', 'warning.png') 114 | 115 | def _error_image(cls): 116 | cls.error_image = os.path.join(__addon__.getAddonInfo('path'), 'resources', 'media', 'error.png') 117 | 118 | def _limit(cls): 119 | cls.limit = 20 120 | 121 | def _last_update_id(cls): 122 | cls.last_update_id = __addon__.getSetting("last_update_id") 123 | 124 | def _fsencoding(cls): 125 | cls.fsencoding = sys.getfilesystemencoding() or 'utf-8' 126 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/settings/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from kodipopcorntime.exceptions import ClassError 3 | from kodipopcorntime.logging import log 4 | 5 | class _MetaClass(type): 6 | def __getattr__(cls, name): 7 | # Do we have a setting method 8 | if not hasattr(cls, '_%s' %name): 9 | raise AttributeError("type object '%s' has no attribute '%s'" %(cls.__name__.lower(), name)) 10 | 11 | # Create setting 12 | getattr(cls, '_%s' %name)() 13 | 14 | # Return setting 15 | value = getattr(cls, name) 16 | log("(Settings) %s.%s is '%s'" %(cls.__name__.lower(), name, str(value))) 17 | return value 18 | 19 | class _Base(object): 20 | def __new__(self, *args, **kw): 21 | raise ClassError("%s is a static class and cannot be initiated" % self.__name__.lower()) 22 | 23 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/settings/base2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import xbmc, sys, os, time, stat, shutil 3 | from . import SUBTITLE_ISO, QUALITIES, PUBLIC_TRACKERS 4 | from .addon import Addon 5 | from .base import _Base, _MetaClass 6 | from urlparse import urlparse 7 | from kodipopcorntime.platform import Platform 8 | from kodipopcorntime.exceptions import Error, Notify 9 | from kodipopcorntime.logging import log, LOGLEVEL 10 | 11 | __addon__ = sys.modules['__main__'].__addon__ 12 | 13 | class _MetaClass2(_MetaClass): 14 | def _mediaType(cls): 15 | cls.mediaType = cls.__name__.lower() 16 | 17 | def _lastchanged(cls): 18 | cls.lastchanged = cls.subtitle_lastchanged > cls.metadata_lastchanged and cls.subtitle_lastchanged or cls.metadata_lastchanged 19 | 20 | def _subtitle_lastchanged(cls): 21 | _name = cls.mediaType 22 | _time = str(time.time()) 23 | for _s in ['preferred_subtitles', 'prioritere_impaired', 'subtitles_provider']: 24 | _tmp = str(getattr(cls, _s)) 25 | if not _tmp == __addon__.getSetting('%s_%s_old' %(_name, _s)): 26 | __addon__.setSetting('%s_%s_old' %(_name, _s), _tmp) 27 | __addon__.setSetting('%s_subtitle_lastchanged' %_name, _time) 28 | 29 | cls.subtitle_lastchanged = float(__addon__.getSetting("%s_subtitle_lastchanged" %_name) or 0.0) 30 | 31 | def _metadata_lastchanged(cls): 32 | _name = cls.mediaType 33 | _time = str(time.time()) 34 | 35 | _tmp = str(cls.metadata_provider) 36 | if not _tmp == __addon__.getSetting('%s_metadata_provider_old' %_name): 37 | __addon__.setSetting("%s_metadata_provider_old" %_name, _tmp) 38 | __addon__.setSetting("%s_metadata_lastchanged" %_name, _time) 39 | 40 | if not Addon.language == __addon__.getSetting('%s_syslang_old' %_name): 41 | __addon__.setSetting("%s_syslang_old" %_name, Addon.language) 42 | __addon__.setSetting("%s_metadata_lastchanged" %_name, _time) 43 | 44 | cls.metadata_lastchanged = float(__addon__.getSetting("%s_metadata_lastchanged" %_name) or 0.0) 45 | 46 | def _preferred_subtitles(cls): 47 | subtitles = [] 48 | if cls.subtitles_provider: 49 | for i in xrange(3): 50 | _n = int(__addon__.getSetting('%s_subtitle_language%d' %(cls.mediaType, i))) 51 | if not _n > 0: 52 | break 53 | subtitles = subtitles+[SUBTITLE_ISO[_n-1]] 54 | cls.preferred_subtitles = subtitles 55 | 56 | def _prioritere_impaired(cls): 57 | if not __addon__.getSetting('%s_subtitle_language1' %cls.mediaType) == '0' and __addon__.getSetting("hearing_impaired") == 'true': 58 | cls.prioritere_impaired = True 59 | else: 60 | cls.prioritere_impaired = False 61 | 62 | def _proxies(cls): 63 | p = [] 64 | domains = __addon__.getSetting("%s_proxies" %cls.mediaType).split(',') 65 | # Evaluate user domains 66 | for domain in reversed(domains): 67 | if not domain[:4] == 'http' and not domain[:2] == '//': 68 | domain = "http://"+domain 69 | d = urlparse(domain) 70 | if d.netloc: 71 | p.append(u"%s://%s/%s" %(d.scheme, d.netloc, d.path)) 72 | cls.proxies = p 73 | 74 | def _qualities(cls): 75 | __qualities = [] 76 | if cls.play3d > 0: 77 | __qualities = [QUALITIES[0]] 78 | if __addon__.getSetting("%s_quality" %cls.mediaType) == '0': 79 | cls.qualities = __qualities+QUALITIES[-2:] 80 | else: 81 | cls.qualities = __qualities+QUALITIES[1:] 82 | 83 | def _play3d(cls): 84 | if not __addon__.getSetting("%s_quality" %cls.mediaType) == '0': 85 | cls.play3d = int(__addon__.getSetting("%s_play3d" %cls.mediaType)) 86 | else: 87 | cls.play3d = 0 88 | 89 | def _media_cache_path(cls): 90 | _path = os.path.join(Addon.cache_path, cls.mediaType) 91 | if not os.path.exists(_path): 92 | os.makedirs(_path) 93 | if not os.path.exists(_path): 94 | raise Error("Unable to create cache directory %s" % _path, 30322) 95 | cls.media_cache_path = _path 96 | 97 | def _user_download_path(cls): 98 | _path = xbmc.translatePath(__addon__.getSetting("%s_download_path" %cls.mediaType)) 99 | if _path: 100 | if _path.lower().startswith("smb://"): 101 | if Platform.system != "windows": 102 | raise Notify("Downloading to an unmounted network share is not supported (%s)" % _path, 30319, 0) 103 | _path.replace("smb:", "").replace("/", "\\") 104 | 105 | if not os.path.isdir(_path): 106 | raise Notify('Download path does not exist (%s)' % _path, 30310, 1) 107 | 108 | cls.user_download_path = _path.encode(Addon.fsencoding) 109 | else: 110 | cls.user_download_path = None 111 | 112 | def _download_path(cls): 113 | cls.download_path = cls.user_download_path or cls.media_cache_path 114 | 115 | def _delete_files(cls): 116 | if cls.keep_files or cls.keep_complete or cls.keep_incomplete: 117 | cls.delete_files = False 118 | else: 119 | cls.delete_files = True 120 | 121 | def _keep_files(cls): 122 | if __addon__.getSetting("%s_keep_files" %cls.mediaType) == 'true' and not cls.keep_complete and not cls.keep_incomplete: 123 | cls.keep_files = True 124 | else: 125 | cls.keep_files = False 126 | 127 | def _keep_complete(cls): 128 | if not __addon__.getSetting("%s_keep_incomplete" %cls.mediaType) == 'false' and __addon__.getSetting("%s_keep_complete" %cls.mediaType) == 'true': 129 | cls.keep_complete = True 130 | else: 131 | cls.keep_complete = False 132 | 133 | def _keep_incomplete(cls): 134 | if __addon__.getSetting("%s_keep_incomplete" %cls.mediaType) == 'true' and __addon__.getSetting("%s_keep_complete" %cls.mediaType) == 'false': 135 | cls.keep_incomplete = True 136 | else: 137 | cls.keep_incomplete = False 138 | 139 | def _binary_path(cls): 140 | binary = "torrent2http" 141 | if Platform.system == 'windows': 142 | binary = "torrent2http.exe" 143 | 144 | binary_path = os.path.join(__addon__.getAddonInfo('path'), 'resources', 'bin', "%s_%s" %(Platform.system, Platform.arch), binary).encode(Addon.fsencoding) 145 | 146 | if Platform.system == "android": 147 | existBinary(binary_path) 148 | binary_path = ensure_android_binary_location(binary_path, os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(xbmc.translatePath('special://xbmc')))), "files", __addon__.getAddonInfo('id'), binary).encode(Addon.fsencoding)) 149 | 150 | existBinary(binary_path) 151 | ensure_exec(binary_path) 152 | 153 | cls.binary_path = binary_path 154 | 155 | def _download_kbps(cls): 156 | download_kbps = int(__addon__.getSetting("download_kbps")) 157 | if download_kbps <= 0: 158 | download_kbps = -1 159 | cls.download_kbps = download_kbps 160 | 161 | def _upload_kbps(cls): 162 | upload_kbps = int(__addon__.getSetting("upload_kbps")) 163 | if upload_kbps <= 0: 164 | upload_kbps = -1 165 | elif upload_kbps < 15: 166 | raise Notify('Max Upload Rate must be above 15 Kilobytes per second.', 30324, 1) 167 | upload_kbps = 15 168 | cls.upload_kbps = upload_kbps 169 | 170 | def _trackers(cls): 171 | trackers = __addon__.getSetting('trackers') 172 | if trackers: 173 | trackers = ",".join(trackers.split(',')+PUBLIC_TRACKERS) 174 | else: 175 | trackers = ",".join(PUBLIC_TRACKERS) 176 | cls.trackers = trackers 177 | 178 | def _torrent_options(cls): 179 | debug = __addon__.getSetting("debug") 180 | kwargs = { 181 | '--bind': '127.0.0.1:5001', 182 | '--dl-path': cls.download_path, 183 | '--connections-limit': int(__addon__.getSetting('connections_limit')), 184 | '--dl-rate': cls.download_kbps, 185 | '--ul-rate': cls.upload_kbps, 186 | '--enable-dht': __addon__.getSetting('enable_dht'), 187 | '--enable-lsd': __addon__.getSetting('enable_lsd'), 188 | '--enable-natpmp': __addon__.getSetting('enable_natpmp'), 189 | '--enable-upnp': __addon__.getSetting('enable_upnp'), 190 | '--enable-scrape': __addon__.getSetting('enable_scrape'), 191 | '--encryption': int(__addon__.getSetting('encryption')), 192 | '--show-stats': debug, 193 | '--files-progress': debug, 194 | '--overall-progress': debug, 195 | '--pieces-progress': debug, 196 | '--listen-port': int(__addon__.getSetting('listen_port')), 197 | '--random-port': __addon__.getSetting('use_random_port'), 198 | '--keep-complete': str(cls.keep_complete).lower(), 199 | '--keep-incomplete': str(cls.keep_incomplete).lower(), 200 | '--keep-files': str(cls.keep_files).lower(), 201 | '--max-idle': 300, 202 | '--no-sparse': 'false', 203 | #'--resume-file': None, 204 | '--user-agent': 'torrent2http/1.0.1 libtorrent/1.0.3.0 kodipopcorntime/%s' %Addon.version, 205 | #'--state-file': None, 206 | '--enable-utp': __addon__.getSetting('enable_utp'), 207 | '--enable-tcp': __addon__.getSetting('enable_tcp'), 208 | '--debug-alerts': debug, 209 | '--torrent-connect-boost': int(__addon__.getSetting('torrent_connect_boost')), 210 | '--connection-speed': int(__addon__.getSetting('connection_speed')), 211 | '--peer-connect-timeout': int(__addon__.getSetting('peer_connect_timeout')), 212 | '--request-timeout': 20, 213 | '--min-reconnect-time': int(__addon__.getSetting('min_reconnect_time')), 214 | '--max-failcount': int(__addon__.getSetting('max_failcount')), 215 | '--dht-routers': __addon__.getSetting('dht_routers') or None, 216 | '--trackers': cls.trackers 217 | } 218 | 219 | args = [cls.binary_path] 220 | for k, v in kwargs.iteritems(): 221 | if v == 'true': 222 | args.append(k) 223 | elif v == 'false': 224 | args.append("%s=false" % k) 225 | elif v is not None: 226 | args.append(k) 227 | if isinstance(v, str): 228 | args.append(v.decode('utf-8').encode(Addon.fsencoding)) 229 | else: 230 | args.append(str(v)) 231 | 232 | cls.torrent_options = args 233 | 234 | class _Base2(_Base): 235 | @classmethod 236 | def get_torrent_options(self, magnet, port): 237 | args = ['--uri', magnet, '--bind', '127.0.0.1:%s' %port] 238 | for i in xrange(4): 239 | if isinstance(args[i], str): 240 | args[i] = args[i].decode('utf-8') 241 | args[i] = args[i].encode(Addon.fsencoding) 242 | return self.torrent_options+args 243 | 244 | def load_provider(module): 245 | provider = "kodipopcorntime.providers.%s" % module 246 | mod = __import__(provider) 247 | for comp in provider.split('.')[1:]: 248 | mod = getattr(mod, comp) 249 | return mod 250 | 251 | def existBinary(binary_path): 252 | if not os.path.isfile(binary_path): 253 | raise Error("torrent2http binary was not found at path %s" % os.path.dirname(binary_path), 30320) 254 | 255 | def ensure_android_binary_location(binary_path, android_binary_path): 256 | log("Trying to copy torrent2http to ext4, since the sdcard is noexec", LOGLEVEL.INFO) 257 | if not os.path.exists(os.path.dirname(android_binary_path)): 258 | os.makedirs(os.path.dirname(android_binary_path)) 259 | if not os.path.exists(android_binary_path) or int(os.path.getmtime(android_binary_path)) < int(os.path.getmtime(binary_path)): 260 | shutil.copy2(binary_path, android_binary_path) 261 | return android_binary_path 262 | 263 | def ensure_exec(binary_path): 264 | st = os.stat(binary_path) 265 | os.chmod(binary_path, st.st_mode | stat.S_IEXEC) 266 | st = os.stat(binary_path) 267 | if not st.st_mode & stat.S_IEXEC: 268 | raise Error("Cannot make torrent2http executable (%s)" % binary_path, 30321) 269 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/settings/movies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | from .base2 import _Base2, _MetaClass2, load_provider 4 | 5 | __addon__ = sys.modules['__main__'].__addon__ 6 | 7 | class Movies(_Base2): 8 | class __metaclass__(_MetaClass2): 9 | def _provider(cls): 10 | cls.provider = load_provider('api-fetch') 11 | 12 | def _metadata_provider(cls): 13 | provider = __addon__.getSetting('movies_metadata_provider') 14 | if not provider == '0': 15 | _list = ['metadata_tmdb'] 16 | cls.metadata_provider = load_provider('movies.%s' % _list[int(provider)-1]) 17 | else: 18 | cls.metadata_provider = None 19 | 20 | def _subtitles_provider(cls): 21 | provider = __addon__.getSetting('movies_subtitle_provider') 22 | if not provider == '0' and not __addon__.getSetting('movies_subtitle_language0') == '0': 23 | _list = ['subtitle_yify'] 24 | cls.subtitles_provider = load_provider('movies.%s' % _list[int(provider)-1]) 25 | else: 26 | cls.subtitles_provider = None 27 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/threads.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys, threading, time 3 | 4 | class Thread(threading.Thread): 5 | LOCAL = threading.local() 6 | 7 | def __init__(self, target=None): 8 | self._target = target or self.run 9 | self._exc_info = [] 10 | 11 | super(Thread, self).__init__(target=self.___run) 12 | self.daemon = False 13 | self.stop = threading.Event() 14 | self.start() 15 | 16 | def __enter__(self): 17 | return self 18 | 19 | def ___run(self): 20 | try: 21 | Thread.LOCAL.tName = self.getName() 22 | self._target() 23 | except: 24 | self._exc_info = sys.exc_info() 25 | sys.exc_clear() 26 | self.stop.set() 27 | 28 | def checkError(self): 29 | return len(self._exc_info) > 0 30 | 31 | def raiseAnyError(self): 32 | if self._exc_info: 33 | raise self._exc_info[0], self._exc_info[1], self._exc_info[2] 34 | 35 | def cleanError(self): 36 | self._exc_info = [] 37 | 38 | def getError(self): 39 | return self._exc_info 40 | 41 | def __exit__(self, *exc_info): 42 | self.close() 43 | return not exc_info[0] and not len(self._exc_info) > 0 44 | 45 | def __del__(self): 46 | self.close() 47 | 48 | def close(self): 49 | if hasattr(self, 'stop') and not self.stop.is_set(): 50 | self.stop.set() 51 | self.raiseAnyError() 52 | 53 | class FLock: 54 | _locks = {} 55 | def __init__(self, file): 56 | if not FLock._locks.get(file): 57 | FLock._locks[file] = False 58 | while FLock._locks[file]: 59 | time.sleep(0.100) 60 | if FLock._locks[file]: 61 | raise RuntimeError("cannot acquired lock") 62 | FLock._locks[file] = True 63 | 64 | def unLock(self, file): 65 | if not FLock._locks[file]: 66 | raise RuntimeError("cannot release un-acquired lock") 67 | FLock._locks[file] = False 68 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/kodipopcorntime/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import xbmcgui, xbmc, simplejson, sys, time, os, UserDict, socket, glob 3 | from urllib import urlencode 4 | from kodipopcorntime.exceptions import Error 5 | from kodipopcorntime.logging import log, log_error, LOGLEVEL 6 | from kodipopcorntime import settings 7 | from kodipopcorntime.threads import FLock 8 | 9 | __addon__ = sys.modules['__main__'].__addon__ 10 | 11 | class NOTIFYLEVEL: 12 | INFO = 0 13 | WARNING = 1 14 | ERROR = 2 15 | 16 | def notify(messageID=0, message=None, level=NOTIFYLEVEL.INFO): 17 | if level == NOTIFYLEVEL.WARNING: 18 | image = settings.addon.warning_image 19 | elif level == NOTIFYLEVEL.ERROR: 20 | image = settings.addon.error_image 21 | else: 22 | image = settings.addon.info_image 23 | 24 | if not message: 25 | message = __addon__.getLocalizedString(messageID) 26 | 27 | try: 28 | xbmc.executebuiltin('XBMC.Notification("%s", "%s", "%s", "%s")' % (settings.addon.name, message, len(message)*210, image)) 29 | except Exception as e: 30 | log('(Utils) Notification failed: %s' % (str(e)), LOGLEVEL.ERROR) 31 | 32 | def xbmcItem(label='', label2='', icon=None, thumbnail=None, path=None, info=None, 33 | info_type='video', properties=None, stream_info=None, 34 | context_menu=None, replace_context_menu=False): 35 | 36 | _listitem = xbmcgui.ListItem(label, label2, icon, thumbnail, path) 37 | if info: 38 | _listitem.setInfo(info_type, info) 39 | if stream_info: 40 | for stream_type, stream_values in stream_info.items(): 41 | _listitem.addStreamInfo(stream_type, stream_values) 42 | if properties: 43 | for key, val in properties.items(): 44 | _listitem.setProperty(key, val) 45 | if context_menu: 46 | _listitem.addContextMenuItems(context_menu, replace_context_menu) 47 | return _listitem 48 | 49 | class Cache(UserDict.DictMixin): 50 | def __init__(self, filename, ttl=0, readOnly=False, last_changed=0): 51 | self._path = os.path.join(settings.addon.cache_path, filename) 52 | self._readOnly = readOnly 53 | self._db = {} 54 | self._lock = FLock(self._path) 55 | 56 | if os.path.isfile(self._path): 57 | with open(self._path, "r") as _o: 58 | self._db = _o.read() 59 | 60 | if self._readOnly: 61 | self._lock.unLock(self._path) 62 | 63 | if self._db: 64 | self._db = simplejson.loads(self._db) 65 | 66 | if not self._db or not ttl == 0 and (time.time() - self._db["created_at"]) > ttl or self._db["created_at"] < last_changed: 67 | self._db = { 68 | "created_at": time.time(), 69 | "data": {} 70 | } 71 | 72 | def __enter__(self): 73 | return self 74 | 75 | def __contains__(self, key): 76 | return key in self._db['data'] 77 | 78 | def has_key(self, key): 79 | return key in self._db['data'] 80 | 81 | def __getitem__(self, key): 82 | return self._db['data'][key] 83 | 84 | def get(self, key, default=None): 85 | try: 86 | return self._db['data'][key] 87 | except KeyError: 88 | sys.exc_clear() 89 | return default 90 | 91 | def copy(self): 92 | return self._db['data'] 93 | 94 | def __setitem__(self, key, value): 95 | self._db['data'][key] = value 96 | 97 | def extendKey(self, key, value): 98 | try: 99 | self._db['data'][key] = self._db['data'][key] + value 100 | except KeyError: 101 | sys.exc_clear() 102 | self._db['data'][key] = value 103 | 104 | def __delitem__(self, key): 105 | del self._db['data'][key] 106 | 107 | def trunctate(self, data={}): 108 | self._db['data'] = dict(data) 109 | 110 | def __iter__(self): 111 | for k in self._db['data'].keys(): 112 | yield k 113 | 114 | def keys(self): 115 | return self._db['data'].keys() 116 | 117 | def __len__(self): 118 | return len(self._db['data']) 119 | 120 | def __nonzero__(self): 121 | if len(self._db['data']) > 0: 122 | return True 123 | return False 124 | 125 | def __str__(self): 126 | return str(self._db['data']) 127 | 128 | def format(self): 129 | return self._build_str(self._db['data']) 130 | 131 | def _build_str(self, cache, level=0): 132 | pieces = [] 133 | tabs = "" 134 | for i in xrange(level): 135 | tabs = tabs+'\t' 136 | joinStr = '%s\t, %s\t\n' %(tabs, tabs) 137 | if isinstance(cache, dict): 138 | for key, value in cache.items(): 139 | pieces.append("'%s': %s" %(key, self._build_str(value), level+1)) 140 | return '%s{\n%s\t%s\n%s}\n' %(tabs, tabs, joinStr.join(pieces), tabs) 141 | elif isinstance(cache, list): 142 | for value in cache: 143 | pieces.append('%s' %(self._build_str(value), level+1)) 144 | return '%s[\n%s\t%s\n%s]\n' %(tabs, tabs, joinStr.join(pieces), tabs) 145 | return str(cache) 146 | 147 | def __exit__(self, *exc_info): 148 | self.close() 149 | return not exc_info[0] 150 | 151 | def __del__(self): 152 | if hasattr(self, '_db'): 153 | self.close() 154 | 155 | def close(self): 156 | if not self._readOnly and self._db: 157 | with open(self._path, "w") as _o: 158 | _o.write(simplejson.dumps(self._db, encoding='UTF-8')) 159 | self._lock.unLock(self._path) 160 | self._lock = None 161 | self._db = {} 162 | 163 | # Sometimes, when we do things too fast for XBMC, it doesn't like it. 164 | # Sometimes, it REALLY doesn't like it. 165 | # This class is here to make sure we are slow enough. 166 | class SafeDialogProgress(xbmcgui.DialogProgress): 167 | def __init__(self): 168 | self._mentions = 0 169 | self._counter = 0 170 | super(SafeDialogProgress, self).__init__() 171 | 172 | def create(self, *args, **kwargs): 173 | time.sleep(0.750) 174 | super(SafeDialogProgress, self).create(*args, **kwargs) 175 | 176 | def set_mentions(self, number): 177 | """ set jobs for progress """ 178 | self._mentions = number 179 | 180 | def update(self, count=0, *args, **kwargs): 181 | percent = 0 182 | self._counter = self._counter+count 183 | if self._mentions: 184 | percent = int(self._counter*100/self._mentions) 185 | if percent > 100: 186 | percent = 100 187 | super(SafeDialogProgress, self).update(percent, *args, **kwargs) 188 | 189 | def __del__(self): 190 | if hasattr(self, '_counter'): 191 | super(SafeDialogProgress, self).close() 192 | 193 | class Dialog(xbmcgui.Dialog): 194 | def yesno(self, line1='', line2='', line3='', heading='', nolabel='', yeslabel='', 195 | lineStr1='', lineStr2='', lineStr3='', headingStr='', nolabelStr='', yeslabelStr='', 196 | autoclose=0): 197 | if heading: 198 | headingStr = __addon__.getLocalizedString(heading) 199 | elif not headingStr: 200 | headingStr = settings.addon.name 201 | if line1: 202 | lineStr1 = __addon__.getLocalizedString(line1) 203 | if line2: 204 | lineStr2 = __addon__.getLocalizedString(line2) 205 | if line3: 206 | lineStr3 = __addon__.getLocalizedString(line3) 207 | if nolabel: 208 | nolabelStr = __addon__.getLocalizedString(nolabel) 209 | if yeslabel: 210 | yeslabelStr = __addon__.getLocalizedString(yeslabel) 211 | 212 | return super(Dialog, self).yesno(headingStr, lineStr1, lineStr2, lineStr3, nolabelStr, yeslabelStr, autoclose) 213 | 214 | def cleanDictList(DictList): 215 | if isinstance(DictList, dict): 216 | return dict((k, v) for k, v in dict((key, cleanDictList(value)) for key, value in DictList.items() if value).items() if v) 217 | if isinstance(DictList, list): 218 | return [v for v in [cleanDictList(value) for value in DictList if value] if v] 219 | return DictList 220 | 221 | def isoToLang(iso): 222 | translateID = settings.ISOTRANSLATEINDEX.get(iso) 223 | if translateID: 224 | return __addon__.getLocalizedString(translateID) 225 | return None 226 | 227 | def build_magnetFromMeta(torrent_hash, dn): 228 | return "%s&%s" % (torrent_hash, urlencode({'dn' : dn}, doseq=True)) 229 | #return "magnet:?xt=urn:btih:%s&%s" % (torrent_hash, urlencode({'dn' : dn}, doseq=True)) 230 | 231 | def get_free_port(port=5001): 232 | """ 233 | Check we can bind to localhost with a specified port 234 | On failer find a new TCP port that can be used for binding 235 | """ 236 | try: 237 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 238 | s.bind(('127.0.0.1', port)) 239 | s.close() 240 | except socket.error: 241 | try: 242 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 243 | s.bind(('127.0.0.1', 0)) 244 | port = s.getsockname()[1] 245 | s.close() 246 | except socket.error: 247 | raise Error("Can not find a TCP port to bind torrent2http", 30300) 248 | return port 249 | 250 | BYTEABBR = [ 251 | 'B', 252 | 'kB', 253 | 'MB', 254 | 'GB', 255 | 'TB', 256 | 'PB', 257 | 'EB', 258 | 'ZB', 259 | 'YB' 260 | ] 261 | 262 | def shortenBytes(byts): 263 | for i in xrange(9): 264 | _B = byts/1024.0 265 | if _B < 1: 266 | return "%.1f %s" %(byts, BYTEABBR[i]) 267 | byts = _B 268 | return "" 269 | 270 | def clear_cache(): 271 | for file in glob.glob('%s/*' %settings.addon.cache_path): 272 | if os.path.isfile(file): 273 | os.remove(file) 274 | 275 | def clear_media_cache(path): 276 | for x in os.listdir(path): 277 | if x in ['.', '..']: 278 | continue 279 | _path = os.path.join(path, x) 280 | if os.path.isfile(_path): 281 | os.remove(_path) 282 | elif os.path.isdir(_path): 283 | clear_media_cache(_path) 284 | os.rmdir(_path) 285 | 286 | def cleanDebris(): 287 | try: 288 | for _mediaType in settings.MEDIATYPES: 289 | _m = getattr(settings, _mediaType) 290 | if _m and _m.delete_files and os.path.isdir(_m.media_cache_path): 291 | clear_media_cache(_m.media_cache_path) 292 | except: 293 | log_error() 294 | sys.exc_clear() 295 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/simplejson/compat.py: -------------------------------------------------------------------------------- 1 | """Python 3 compatibility shims 2 | """ 3 | import sys 4 | if sys.version_info[0] < 3: 5 | PY3 = False 6 | def b(s): 7 | return s 8 | def u(s): 9 | return unicode(s, 'unicode_escape') 10 | import cStringIO as StringIO 11 | StringIO = BytesIO = StringIO.StringIO 12 | text_type = unicode 13 | binary_type = str 14 | string_types = (basestring,) 15 | integer_types = (int, long) 16 | unichr = unichr 17 | reload_module = reload 18 | def fromhex(s): 19 | return s.decode('hex') 20 | 21 | else: 22 | PY3 = True 23 | if sys.version_info[:2] >= (3, 4): 24 | from importlib import reload as reload_module 25 | else: 26 | from imp import reload as reload_module 27 | import codecs 28 | def b(s): 29 | return codecs.latin_1_encode(s)[0] 30 | def u(s): 31 | return s 32 | import io 33 | StringIO = io.StringIO 34 | BytesIO = io.BytesIO 35 | text_type = str 36 | binary_type = bytes 37 | string_types = (str,) 38 | integer_types = (int,) 39 | 40 | def unichr(s): 41 | return u(chr(s)) 42 | 43 | def fromhex(s): 44 | return bytes.fromhex(s) 45 | 46 | long_type = integer_types[-1] 47 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/simplejson/ordered_dict.py: -------------------------------------------------------------------------------- 1 | """Drop-in replacement for collections.OrderedDict by Raymond Hettinger 2 | 3 | http://code.activestate.com/recipes/576693/ 4 | 5 | """ 6 | from UserDict import DictMixin 7 | 8 | # Modified from original to support Python 2.4, see 9 | # http://code.google.com/p/simplejson/issues/detail?id=53 10 | try: 11 | all 12 | except NameError: 13 | def all(seq): 14 | for elem in seq: 15 | if not elem: 16 | return False 17 | return True 18 | 19 | class OrderedDict(dict, DictMixin): 20 | 21 | def __init__(self, *args, **kwds): 22 | if len(args) > 1: 23 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 24 | try: 25 | self.__end 26 | except AttributeError: 27 | self.clear() 28 | self.update(*args, **kwds) 29 | 30 | def clear(self): 31 | self.__end = end = [] 32 | end += [None, end, end] # sentinel node for doubly linked list 33 | self.__map = {} # key --> [key, prev, next] 34 | dict.clear(self) 35 | 36 | def __setitem__(self, key, value): 37 | if key not in self: 38 | end = self.__end 39 | curr = end[1] 40 | curr[2] = end[1] = self.__map[key] = [key, curr, end] 41 | dict.__setitem__(self, key, value) 42 | 43 | def __delitem__(self, key): 44 | dict.__delitem__(self, key) 45 | key, prev, next = self.__map.pop(key) 46 | prev[2] = next 47 | next[1] = prev 48 | 49 | def __iter__(self): 50 | end = self.__end 51 | curr = end[2] 52 | while curr is not end: 53 | yield curr[0] 54 | curr = curr[2] 55 | 56 | def __reversed__(self): 57 | end = self.__end 58 | curr = end[1] 59 | while curr is not end: 60 | yield curr[0] 61 | curr = curr[1] 62 | 63 | def popitem(self, last=True): 64 | if not self: 65 | raise KeyError('dictionary is empty') 66 | # Modified from original to support Python 2.4, see 67 | # http://code.google.com/p/simplejson/issues/detail?id=53 68 | if last: 69 | key = reversed(self).next() 70 | else: 71 | key = iter(self).next() 72 | value = self.pop(key) 73 | return key, value 74 | 75 | def __reduce__(self): 76 | items = [[k, self[k]] for k in self] 77 | tmp = self.__map, self.__end 78 | del self.__map, self.__end 79 | inst_dict = vars(self).copy() 80 | self.__map, self.__end = tmp 81 | if inst_dict: 82 | return (self.__class__, (items,), inst_dict) 83 | return self.__class__, (items,) 84 | 85 | def keys(self): 86 | return list(self) 87 | 88 | setdefault = DictMixin.setdefault 89 | update = DictMixin.update 90 | pop = DictMixin.pop 91 | values = DictMixin.values 92 | items = DictMixin.items 93 | iterkeys = DictMixin.iterkeys 94 | itervalues = DictMixin.itervalues 95 | iteritems = DictMixin.iteritems 96 | 97 | def __repr__(self): 98 | if not self: 99 | return '%s()' % (self.__class__.__name__,) 100 | return '%s(%r)' % (self.__class__.__name__, self.items()) 101 | 102 | def copy(self): 103 | return self.__class__(self) 104 | 105 | @classmethod 106 | def fromkeys(cls, iterable, value=None): 107 | d = cls() 108 | for key in iterable: 109 | d[key] = value 110 | return d 111 | 112 | def __eq__(self, other): 113 | if isinstance(other, OrderedDict): 114 | return len(self)==len(other) and \ 115 | all(p==q for p, q in zip(self.items(), other.items())) 116 | return dict.__eq__(self, other) 117 | 118 | def __ne__(self, other): 119 | return not self == other 120 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/simplejson/scanner.py: -------------------------------------------------------------------------------- 1 | """JSON token scanner 2 | """ 3 | import re 4 | def _import_c_make_scanner(): 5 | try: 6 | from simplejson._speedups import make_scanner 7 | return make_scanner 8 | except ImportError: 9 | return None 10 | c_make_scanner = _import_c_make_scanner() 11 | 12 | __all__ = ['make_scanner', 'JSONDecodeError'] 13 | 14 | NUMBER_RE = re.compile( 15 | r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?', 16 | (re.VERBOSE | re.MULTILINE | re.DOTALL)) 17 | 18 | class JSONDecodeError(ValueError): 19 | """Subclass of ValueError with the following additional properties: 20 | 21 | msg: The unformatted error message 22 | doc: The JSON document being parsed 23 | pos: The start index of doc where parsing failed 24 | end: The end index of doc where parsing failed (may be None) 25 | lineno: The line corresponding to pos 26 | colno: The column corresponding to pos 27 | endlineno: The line corresponding to end (may be None) 28 | endcolno: The column corresponding to end (may be None) 29 | 30 | """ 31 | # Note that this exception is used from _speedups 32 | def __init__(self, msg, doc, pos, end=None): 33 | ValueError.__init__(self, errmsg(msg, doc, pos, end=end)) 34 | self.msg = msg 35 | self.doc = doc 36 | self.pos = pos 37 | self.end = end 38 | self.lineno, self.colno = linecol(doc, pos) 39 | if end is not None: 40 | self.endlineno, self.endcolno = linecol(doc, end) 41 | else: 42 | self.endlineno, self.endcolno = None, None 43 | 44 | def __reduce__(self): 45 | return self.__class__, (self.msg, self.doc, self.pos, self.end) 46 | 47 | 48 | def linecol(doc, pos): 49 | lineno = doc.count('\n', 0, pos) + 1 50 | if lineno == 1: 51 | colno = pos + 1 52 | else: 53 | colno = pos - doc.rindex('\n', 0, pos) 54 | return lineno, colno 55 | 56 | 57 | def errmsg(msg, doc, pos, end=None): 58 | lineno, colno = linecol(doc, pos) 59 | msg = msg.replace('%r', repr(doc[pos:pos + 1])) 60 | if end is None: 61 | fmt = '%s: line %d column %d (char %d)' 62 | return fmt % (msg, lineno, colno, pos) 63 | endlineno, endcolno = linecol(doc, end) 64 | fmt = '%s: line %d column %d - line %d column %d (char %d - %d)' 65 | return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end) 66 | 67 | 68 | def py_make_scanner(context): 69 | parse_object = context.parse_object 70 | parse_array = context.parse_array 71 | parse_string = context.parse_string 72 | match_number = NUMBER_RE.match 73 | encoding = context.encoding 74 | strict = context.strict 75 | parse_float = context.parse_float 76 | parse_int = context.parse_int 77 | parse_constant = context.parse_constant 78 | object_hook = context.object_hook 79 | object_pairs_hook = context.object_pairs_hook 80 | memo = context.memo 81 | 82 | def _scan_once(string, idx): 83 | errmsg = 'Expecting value' 84 | try: 85 | nextchar = string[idx] 86 | except IndexError: 87 | raise JSONDecodeError(errmsg, string, idx) 88 | 89 | if nextchar == '"': 90 | return parse_string(string, idx + 1, encoding, strict) 91 | elif nextchar == '{': 92 | return parse_object((string, idx + 1), encoding, strict, 93 | _scan_once, object_hook, object_pairs_hook, memo) 94 | elif nextchar == '[': 95 | return parse_array((string, idx + 1), _scan_once) 96 | elif nextchar == 'n' and string[idx:idx + 4] == 'null': 97 | return None, idx + 4 98 | elif nextchar == 't' and string[idx:idx + 4] == 'true': 99 | return True, idx + 4 100 | elif nextchar == 'f' and string[idx:idx + 5] == 'false': 101 | return False, idx + 5 102 | 103 | m = match_number(string, idx) 104 | if m is not None: 105 | integer, frac, exp = m.groups() 106 | if frac or exp: 107 | res = parse_float(integer + (frac or '') + (exp or '')) 108 | else: 109 | res = parse_int(integer) 110 | return res, m.end() 111 | elif nextchar == 'N' and string[idx:idx + 3] == 'NaN': 112 | return parse_constant('NaN'), idx + 3 113 | elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity': 114 | return parse_constant('Infinity'), idx + 8 115 | elif nextchar == '-' and string[idx:idx + 9] == '-Infinity': 116 | return parse_constant('-Infinity'), idx + 9 117 | else: 118 | raise JSONDecodeError(errmsg, string, idx) 119 | 120 | def scan_once(string, idx): 121 | if idx < 0: 122 | # Ensure the same behavior as the C speedup, otherwise 123 | # this would work for *some* negative string indices due 124 | # to the behavior of __getitem__ for strings. #98 125 | raise JSONDecodeError('Expecting value', string, idx) 126 | try: 127 | return _scan_once(string, idx) 128 | finally: 129 | memo.clear() 130 | 131 | return scan_once 132 | 133 | make_scanner = c_make_scanner or py_make_scanner 134 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/lib/simplejson/tool.py: -------------------------------------------------------------------------------- 1 | r"""Command-line tool to validate and pretty-print JSON 2 | 3 | Usage:: 4 | 5 | $ echo '{"json":"obj"}' | python -m simplejson.tool 6 | { 7 | "json": "obj" 8 | } 9 | $ echo '{ 1.2:3.4}' | python -m simplejson.tool 10 | Expecting property name: line 1 column 2 (char 2) 11 | 12 | """ 13 | from __future__ import with_statement 14 | import sys 15 | import simplejson as json 16 | 17 | def main(): 18 | if len(sys.argv) == 1: 19 | infile = sys.stdin 20 | outfile = sys.stdout 21 | elif len(sys.argv) == 2: 22 | infile = open(sys.argv[1], 'r') 23 | outfile = sys.stdout 24 | elif len(sys.argv) == 3: 25 | infile = open(sys.argv[1], 'r') 26 | outfile = open(sys.argv[2], 'w') 27 | else: 28 | raise SystemExit(sys.argv[0] + " [infile [outfile]]") 29 | with infile: 30 | try: 31 | obj = json.load(infile, 32 | object_pairs_hook=json.OrderedDict, 33 | use_decimal=True) 34 | except ValueError: 35 | raise SystemExit(sys.exc_info()[1]) 36 | with outfile: 37 | json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True) 38 | outfile.write('\n') 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/background/halloween.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/background/halloween.jpg -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/background/new_year.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/background/new_year.jpg -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/background/valentine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/background/valentine.jpg -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/background/xmass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/background/xmass.jpg -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/black.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/categories/Anime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/categories/Anime.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/categories/Movies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/categories/Movies.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/categories/TVShows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/categories/TVShows.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/error.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/info.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/back.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/back_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/back_thumbnail.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Action.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Adventure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Adventure.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Animation.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Biography.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Biography.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Comedy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Comedy.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Crime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Crime.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Documentary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Documentary.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Drama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Drama.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Family.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Family.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Fantasy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Fantasy.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Film-Noir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Film-Noir.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/History.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/History.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Horror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Horror.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Music.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Musical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Musical.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Mystery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Mystery.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Romance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Romance.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Sci-Fi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Sci-Fi.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Sport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Sport.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Thriller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Thriller.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/War.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/War.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/genres/Western.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/genres/Western.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/more.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/more_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/more_thumbnail.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/popular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/popular.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/rated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/rated.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/recently.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/recently.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/movies/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/movies/search.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/back.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/back_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/back_thumbnail.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Action.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Adventure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Adventure.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Animation.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Biography.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Biography.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Comedy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Comedy.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Crime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Crime.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Documentary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Documentary.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Drama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Drama.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Family.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Family.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Fantasy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Fantasy.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Film-Noir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Film-Noir.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/History.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/History.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Horror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Horror.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Music.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Musical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Musical.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Mystery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Mystery.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Romance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Romance.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Sci-Fi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Sci-Fi.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Sport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Sport.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Thriller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Thriller.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/War.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/War.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/genres/Western.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/genres/Western.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/more.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/more_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/more_thumbnail.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/popular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/popular.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/rated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/rated.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/recently.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/recently.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/tvshows/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/tvshows/search.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/media/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markop159/KODI-Popcorn-Time/f08f69a224a7a2484dff2f4b08bfd0c49981e030/plugin.video.kodipopcorntime/resources/media/warning.png -------------------------------------------------------------------------------- /plugin.video.kodipopcorntime/resources/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | --------------------------------------------------------------------------------