├── .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 |
--------------------------------------------------------------------------------
/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 | [](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 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=83UA43TPBRHLL)
76 |
77 | 
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 |
--------------------------------------------------------------------------------