diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..025ccfc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: python +matrix: + include: + - python: "2.6" + - python: "2.7" + - python: "2.7.10" + - python: "2.7.11" + allow_failures: + - python: "3.2" + - python: "3.3" + - python: "3.4" + - python: "3.5" + - python: "nightly" + +# command to install dependencies +install: + - pip install python-dateutil pytest +# command to run tests +script: py.test -v + diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 11ac83f..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Henry Richard J - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d3c05c --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Kodi Voot Addon + This Kodi addon allows you to watch Movies and TV Shows from Voot OTT platform without Voot Account. + + # My Kodi Repo +Vist Here to get more information about my kodi repo https://henry-richard.ml/Kodi/ + +# Screenshots + + + # My Youtube Channel +[![](https://img.shields.io/badge/Subscribe-red?style=for-the-badge&logo=YouTube)](https://www.youtube.com/channel/UCVGasc5jr45eZUpZNHvbtWQ) + + +# My Telegram Channel +[![](https://img.shields.io/badge/Telegram-Join%20Now-blue?style=for-the-badge&logo=Telegram)](https://t.me/cracked4free) + +## Donations +If you like my projects then consider making a small donation by clicking below button ^_^ +
+[![](https://img.shields.io/badge/Donate-Paypal-blue?style=for-the-badge&logo=paypal)](https://www.paypal.com/paypalme/henryrics) + +# Copyrights © [Henry Richard J](https://github.com/henry-richard7) +#### Star the Repo in case you liked it :) diff --git a/Screenshots/Voot 1.png b/Screenshots/Voot 1.png new file mode 100644 index 0000000..e688fb4 Binary files /dev/null and b/Screenshots/Voot 1.png differ diff --git a/Screenshots/Voot 2.png b/Screenshots/Voot 2.png new file mode 100644 index 0000000..aa6b1aa Binary files /dev/null and b/Screenshots/Voot 2.png differ diff --git a/Screenshots/Voot 3.png b/Screenshots/Voot 3.png new file mode 100644 index 0000000..049484e Binary files /dev/null and b/Screenshots/Voot 3.png differ diff --git a/addon.xml b/addon.xml new file mode 100644 index 0000000..31da1e4 --- /dev/null +++ b/addon.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + video + + + Watch Voot without login. + + + all + MIT + + henry-richard.ml + + + + + + resources/icon.png + resources/fanart.jpg + + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..c36565f --- /dev/null +++ b/main.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from resources.lib import plugin +if __name__ == "__main__": + plugin.run() \ No newline at end of file diff --git a/resources/__init__.py b/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/resources/fanart.jpg b/resources/fanart.jpg new file mode 100644 index 0000000..8cb2025 Binary files /dev/null and b/resources/fanart.jpg differ diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000..de25dc6 Binary files /dev/null and b/resources/icon.png differ diff --git a/resources/language/README.md b/resources/language/README.md new file mode 100644 index 0000000..b4f8390 --- /dev/null +++ b/resources/language/README.md @@ -0,0 +1 @@ +This folder will be the home of your translations \ No newline at end of file diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po new file mode 100644 index 0000000..65c16c5 --- /dev/null +++ b/resources/language/resource.language.en_gb/strings.po @@ -0,0 +1,27 @@ +# Kodi Media Center language file +# Addon Name: Voot Streamer +# Addon id: plugin.henry.vootstreamer +# Addon Provider: Henry Richard +msgid "" +msgstr "" +"Project-Id-Version: XBMC Addons\n" +"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Kodi Translation Team\n" +"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#This is a comment + +msgctxt "#32000" +msgid "Example" +msgstr "" + +msgctxt "#32001" +msgid "Debug" +msgstr "" \ No newline at end of file diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/resources/lib/kodilogging.py b/resources/lib/kodilogging.py new file mode 100644 index 0000000..5f42ab7 --- /dev/null +++ b/resources/lib/kodilogging.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals +from resources.lib.kodiutils import get_setting_as_bool + +import logging +import xbmc +import xbmcaddon + + +class KodiLogHandler(logging.StreamHandler): + + def __init__(self): + logging.StreamHandler.__init__(self) + addon_id = xbmcaddon.Addon().getAddonInfo('id') + prefix = b"[%s] " % addon_id + formatter = logging.Formatter(prefix + b'%(name)s: %(message)s') + self.setFormatter(formatter) + + def emit(self, record): + levels = { + logging.CRITICAL: xbmc.LOGFATAL, + logging.ERROR: xbmc.LOGERROR, + logging.WARNING: xbmc.LOGWARNING, + logging.INFO: xbmc.LOGINFO, + logging.DEBUG: xbmc.LOGDEBUG, + logging.NOTSET: xbmc.LOGNONE, + } + if get_setting_as_bool('debug'): + try: + xbmc.log(self.format(record), levels[record.levelno]) + except UnicodeEncodeError: + xbmc.log(self.format(record).encode( + 'utf-8', 'ignore'), levels[record.levelno]) + + def flush(self): + pass + + +def config(): + logger = logging.getLogger() + logger.addHandler(KodiLogHandler()) + logger.setLevel(logging.DEBUG) diff --git a/resources/lib/kodiutils.py b/resources/lib/kodiutils.py new file mode 100644 index 0000000..bbcec90 --- /dev/null +++ b/resources/lib/kodiutils.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +import xbmc +import xbmcaddon +import xbmcgui +import sys +import logging +import json as json + + +# read settings +ADDON = xbmcaddon.Addon() + +logger = logging.getLogger(__name__) + + +def notification(header, message, time=5000, icon=ADDON.getAddonInfo('icon'), sound=True): + xbmcgui.Dialog().notification(header, message, icon, time, sound) + + +def show_settings(): + ADDON.openSettings() + + +def get_setting(setting): + return ADDON.getSetting(setting).strip().decode('utf-8') + + +def set_setting(setting, value): + ADDON.setSetting(setting, str(value)) + + +def get_setting_as_bool(setting): + return get_setting(setting).lower() == "true" + + +def get_setting_as_float(setting): + try: + return float(get_setting(setting)) + except ValueError: + return 0 + + +def get_setting_as_int(setting): + try: + return int(get_setting_as_float(setting)) + except ValueError: + return 0 + + +def get_string(string_id): + return ADDON.getLocalizedString(string_id).encode('utf-8', 'ignore') + + +def kodi_json_request(params): + data = json.dumps(params) + request = xbmc.executeJSONRPC(data) + + try: + response = json.loads(request) + except UnicodeDecodeError: + response = json.loads(request.decode('utf-8', 'ignore')) + + try: + if 'result' in response: + return response['result'] + return None + except KeyError: + logger.warn("[%s] %s" % + (params['method'], response['error']['message'])) + return None diff --git a/resources/lib/plugin.py b/resources/lib/plugin.py new file mode 100644 index 0000000..9ce7cf8 --- /dev/null +++ b/resources/lib/plugin.py @@ -0,0 +1,158 @@ +from __future__ import unicode_literals + +# noinspection PyUnresolvedReferences +from codequick import Route, Resolver, Listitem, run +from codequick.utils import urljoin_partial, bold +import requests +import xbmcgui +import re +import urllib +import inputstreamhelper + +apiUrl = 'https://psapi.voot.com/media/voot/v1/' +headers = { + + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, deflate, br", + "content-type": "application/json; charset=utf-8", + "referer": "https://www.voot.com/", + "usertype": "svod", + "Content-Version": "V4", + "origin": "https://www.voot.com" +} + +languages = ['Hindi', 'Marathi', 'Tamil', 'English', 'Telugu', + 'Kannada', 'Bengali', 'Gujarati', 'Tulu'] + + +def direct_link(movie_id): + response = requests.get( + f"https://wapi.voot.com/ws/ott/getMediaInfo.json?platform=Web&pId=2&mediaId={movie_id}").json() + return response["assets"]["Files"][3]["URL"] + + +@Route.register +def root(plugin, content_type="segment"): + Movies_Languages_item = { + "label": "Voot Movies", + "callback": voot_movies_languages + } + + Shows_Languages_item = { + "label": "Voot Shows", + "callback": voot_shows_languages + } + + yield Listitem.from_dict(**Movies_Languages_item) + yield Listitem.from_dict(**Shows_Languages_item) + + +@Route.register +def voot_movies_languages(plugin): + for language in languages: + item = Listitem() + item.label = language + item.info["plot"] = f"Watch Voot {language} Movies for free!" + item.set_callback(list_voot_movies, lang=language, page_no="1") + yield item + + +@Route.register +def voot_shows_languages(plugin): + for language in languages: + item = Listitem() + item.label = language + item.info["plot"] = f"Watch Voot {language} Shows for free!" + item.set_callback(list_voot_shows, lang=language, page_no="1") + yield item + + +@Route.register +def list_voot_movies(plugin, lang, page_no): + url = '%svoot-web/content/generic/movies-by-language?language=include:%s&sort=mostpopular:desc&&page=%s&responseType=common' % ( + apiUrl, lang, page_no) + movies = requests.get(url, headers=headers).json() + + next_page = int(movies["page"]) + 1 + for movie in movies["result"]: + item = Listitem() + item.label = movie["fullTitle"] + item.art["thumb"] = f'http://v3img.voot.com/{movie["imageUri"]}' + item.art["fanart"] = f'http://v3img.voot.com/{movie["imageUri"]}' + item.info["plot"] = movie["fullSynopsis"] + item.set_callback(play_video, video_id=movie["id"]) + + yield item + yield Listitem.next_page(page_no=next_page, lang=lang, callback=list_voot_movies) + + +@Route.register +def list_voot_shows(plugin, lang, page_no): + url = f"https://psapi.voot.com/jio/voot/v1/voot-web/content/generic/filtered-shows?sort=mostpopular:desc&language=include:{lang}&page={page_no}&responseType=common" + jd = requests.get(url, headers=headers).json() + shows = jd['result'] + next_page = int(jd["page"]) + 1 + for show in shows: + item = Listitem() + item.label = show["fullTitle"] + item.art["thumb"] = f'http://v3img.voot.com/{show["imageUri"]}' + item.art["fanart"] = f'http://v3img.voot.com/{show["imageUri"]}' + item.info["plot"] = show["fullSynopsis"] + item.set_callback(list_seasons, show_id=show["id"], Page_No="1") + + yield item + yield Listitem.next_page(page_no=next_page, lang=lang, callback=list_voot_shows) + + +@Route.register +def list_seasons(plugin, show_id, Page_No): + url = '%svoot-web/content/generic/season-by-show?sort=season:desc&id=%s&page=%s&responseType=common' % ( + apiUrl, show_id, Page_No) + + jd = requests.get(url, headers=headers).json() + next_page = int(jd["page"]) + 1 + seasons = jd["result"] + + for season in seasons: + item = Listitem() + item.label = season["fullTitle"] + item.art["thumb"] = f'http://v3img.voot.com/{season["imageUri"]}' + item.art["fanart"] = f'http://v3img.voot.com/{season["imageUri"]}' + item.info["plot"] = season["fullSynopsis"] + item.set_callback(list_episodes, season_id=season["id"], Page_No="1") + yield item + yield Listitem.next_page(Page_No=next_page, show_id=show_id, callback=list_seasons) + + +@Route.register +def list_episodes(plugin, season_id, Page_No): + url = '%svoot-web/content/generic/series-wise-episode?sort=episode:desc&id=%s&&page=%s&responseType=common' % ( + apiUrl, season_id, Page_No) + + jd = requests.get(url, headers=headers).json() + next_page = int(jd["page"]) + 1 + + episodes = jd["result"] + for episode in episodes: + item = Listitem() + item.label = episode["fullTitle"] + item.art["thumb"] = f'http://v3img.voot.com/{episode["imageUri"]}' + item.art["fanart"] = f'http://v3img.voot.com/{episode["imageUri"]}' + item.info["plot"] = episode["fullSynopsis"] + item.set_callback(play_video, video_id=episode["id"]) + yield item + yield Listitem.next_page(Page_No=next_page, season_id=season_id, callback=list_episodes) + + +@Resolver.register +def play_video(plugin, video_id): + return Listitem().from_dict(**{ + "label": "Playing", + "callback": direct_link(video_id), + "properties": { + "inputstream.adaptive.manifest_type": "hls", + "inputstream": "inputstream.adaptive" + + } + }) diff --git a/resources/settings.xml b/resources/settings.xml new file mode 100644 index 0000000..96f7359 --- /dev/null +++ b/resources/settings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..ad2917c --- /dev/null +++ b/tests/README.md @@ -0,0 +1,2 @@ +# Tests +This folder should be the home for your unit tests \ No newline at end of file