diff --git a/addon.xml b/addon.xml index 2d108c9..86ef693 100644 --- a/addon.xml +++ b/addon.xml @@ -1,10 +1,10 @@ - + - + @@ -22,8 +22,25 @@ https://botallen.com/discord https://botallen.com kodi@botallen.com - https://github.com/krreet/plugin.video.jiotv + https://github.com/tobalan/plugin.video.jiotv +[- 2.3.4 -] +[added] AAC multi-audio fix (3rd Jun 2023) +[added] Genres selection in Settings with Languages +[added] EPG Source URL editable in Settings +[added] Settings sections rearranged, removed 'Welcome-donate' popup. + +[- 2.3.3 -] +[added] updated epg url +[added] set epg cache to false +[added] updset useInputstreamAdaptiveforHls to true + +[- 2.3.2 -] +[added] generate playlist is off by default + +[- 2.3.1 -] +[added] caching of reponses + [- 2.3.0 -] [added] more extra channels [added] inputstream adaptive as a dependency diff --git a/changelog.txt b/changelog.txt index b82d8da..6fa8d2e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,14 @@ +[- 2.3.3 -] +[added] updated epg url +[added] set epg cache to false +[added] updset useInputstreamAdaptiveforHls to true + +[- 2.3.2 -] +[added] generate playlist is off by default + +[- 2.3.1 -] +[added] caching of reponses + [- 2.3.0 -] [added] more extra channels [added] inputstream adaptive as a dependency diff --git a/resources/lib/constants.py b/resources/lib/constants.py index 69d0a8e..cc6718a 100644 --- a/resources/lib/constants.py +++ b/resources/lib/constants.py @@ -15,7 +15,7 @@ FEATURED_SRC = "https://tv.media.jio.com/apis/v1.6/getdata/featurednew?start=0&limit=30&langId=6" CHANNELS_SRC_NEW = "https://jiotv.data.cdn.jio.com/apis/v3.0/getMobileChannelList/get/?langId=6&os=android&devicetype=phone&usertype=tvYR7NSNn7rymo3F&version=285" CHANNELS_SRC = CHANNELS_SRC_NEW if Settings.get_boolean( - "channelsrc") else "https://jiotv.data.cdn.jio.com/apis/v1.3/getMobileChannelList/get/?os=android&devicetype=phone&usertype=JIO&version=290&langId=6" + "channelsrc") else "https://jiotv.data.cdn.jio.com/apis/v1.4/getMobileChannelList/get/?os=android&devicetype=phone&usertype=JIO&version=296&langId=6" GET_CHANNEL_URL = "https://tv.media.jio.com/apis/v2.0/getchannelurl/getchannelurl?langId=6&userLanguages=All" CATCHUP_SRC = "https://jiotv.data.cdn.jio.com/apis/v1.3/getepg/get?offset={0}&channel_id={1}&langId=6" M3U_SRC = os.path.join(translatePath( @@ -24,7 +24,7 @@ ADDON.getAddonInfo("profile")), "jiotv-epg.xml.gz") M3U_CHANNEL = "\n#EXTINF:0 tvg-id=\"{tvg_id}\" tvg-name=\"{channel_name}\" group-title=\"{group_title}\" tvg-chno=\"{tvg_chno}\" tvg-logo=\"{tvg_logo}\"{catchup},{channel_name}\n{play_url}" # EPG_SRC = "https://kodi.botallen.com/tv/epg.xml.gz" -EPG_SRC = "https://cdn.jsdelivr.net/gh/krreet/epg/python/epg.xml.gz" +EPG_SRC = "https://tobalan.github.io/epg.xml.gz" DICTIONARY_URL = "https://jiotvapi.cdn.jio.com/apis/v1.3/dictionary/dictionary?langId=6" IMG_CONFIG = { diff --git a/resources/lib/main.py b/resources/lib/main.py index 08143cf..af42721 100644 --- a/resources/lib/main.py +++ b/resources/lib/main.py @@ -13,8 +13,8 @@ from codequick.storage import PersistentDict # add-on imports -from resources.lib.utils import getTokenParams, getHeaders, isLoggedIn, login as ULogin, logout as ULogout, check_addon, sendOTPV2, get_local_ip, getChannelHeaders, quality_to_enum, _setup, kodi_rpc, Monitor, busy -from resources.lib.constants import GET_CHANNEL_URL, FEATURED_SRC, CHANNELS_SRC, IMG_CATCHUP, PLAY_URL, IMG_CATCHUP_SHOWS, CATCHUP_SRC, M3U_SRC, EPG_SRC, M3U_CHANNEL, DICTIONARY_URL, IMG_CONFIG, EPG_PATH +from resources.lib.utils import getTokenParams, getHeaders, isLoggedIn, login as ULogin, logout as ULogout, check_addon, sendOTPV2, get_local_ip, getChannelHeaders, quality_to_enum, _setup, kodi_rpc, Monitor, getCachedChannels, getCachedDictionary, cleanLocalCache, getFeatured +from resources.lib.constants import GET_CHANNEL_URL, IMG_CATCHUP, PLAY_URL, IMG_CATCHUP_SHOWS, CATCHUP_SRC, M3U_SRC, EPG_SRC, M3U_CHANNEL, IMG_CONFIG, EPG_PATH # additional imports import urlquick @@ -62,13 +62,7 @@ def root(plugin): # Shows Featured Content @Route.register def show_featured(plugin, id=None): - resp = urlquick.get(FEATURED_SRC, headers={ - "usergroup": "tvYR7NSNn7rymo3F", - "os": "android", - "devicetype": "phone", - "versionCode": "290" - }, max_age=-1).json() - for each in resp.get("featuredNewData", []): + for each in getFeatured(): if id: if int(each.get("id", 0)) == int(id): data = each.get("data", []) @@ -140,8 +134,7 @@ def show_featured(plugin, id=None): # Shows Filter options @Route.register def show_listby(plugin, by): - r = urlquick.get(DICTIONARY_URL).text.encode('utf8')[3:].decode('utf8') - dictionary = json.loads(r) + dictionary = getCachedDictionary() GENRE_MAP = dictionary.get("channelCategoryMapping") LANG_MAP = dictionary.get("languageIdMapping") langValues = list(LANG_MAP.values()) @@ -168,9 +161,8 @@ def show_listby(plugin, by): # Shows channels by selected filter/category @Route.register def show_category(plugin, categoryOrLang, by): - resp = urlquick.get(CHANNELS_SRC).json().get("result") - r = urlquick.get(DICTIONARY_URL).text.encode('utf8')[3:].decode('utf8') - dictionary = json.loads(r) + resp = getCachedChannels() + dictionary = getCachedDictionary() GENRE_MAP = dictionary.get("channelCategoryMapping") LANG_MAP = dictionary.get("languageIdMapping") @@ -214,7 +206,7 @@ def fltr(x): def show_epg(plugin, day, channel_id): resp = urlquick.get(CATCHUP_SRC.format(day, channel_id), max_age=-1).json() epg = sorted( - resp['epg'], key=lambda show: show['startEpoch'], reverse=True) + resp['epg'], key=lambda show: show['startEpoch'], reverse=False) livetext = '[COLOR red] [ LIVE ] [/COLOR]' for each in epg: current_epoch = int(time()*1000) @@ -307,78 +299,87 @@ def play(plugin, channel_id, showtime=None, srno=None, programId=None, begin=Non # Script.notify("srno", srno) # Script.notify("showtime", showtime) # Script.notify("channel_id", channel_id) - is_helper = inputstreamhelper.Helper("mpd", drm="com.widevine.alpha") - hasIs = is_helper.check_inputstream() - if not hasIs: - return - rjson = { - "channel_id": int(channel_id), - "stream_type": "Seek" - } - isCatchup = False - if showtime and srno: - isCatchup = True - rjson["showtime"] = showtime - rjson["srno"] = srno - rjson["stream_type"] = "Catchup" - rjson["programId"] = programId - rjson["begin"] = begin - rjson["end"] = end - Script.log(str(rjson), lvl=Script.INFO) - headers = getHeaders() - headers['channelid'] = str(channel_id) - headers['srno'] = str(uuid4()) if "srno" not in rjson else rjson["srno"] - res = urlquick.post(GET_CHANNEL_URL, json=rjson, - headers=getChannelHeaders(), max_age=-1) - resp = res.json() - art = {} - onlyUrl = resp.get("result", "").split("?")[0].split('/')[-1] - art["thumb"] = art["icon"] = IMG_CATCHUP + \ - onlyUrl.replace(".m3u8", ".png") - cookie = "__hdnea__"+resp.get("result", "").split("__hdnea__")[-1] - headers['cookie'] = cookie - uriToUse = resp.get("result", "") - qltyopt = Settings.get_string("quality") - selectionType = "adaptive" - if qltyopt == 'Manual': - selectionType = "ask-quality" - else: - m3u8Headers = {} - m3u8Headers['user-agent'] = headers['user-agent'] - m3u8Headers['cookie'] = cookie - m3u8Res = urlquick.get(uriToUse, headers=m3u8Headers, - max_age=-1, raise_for_status=True) - # Script.notify("m3u8url", m3u8Res.status_code) - m3u8String = m3u8Res.text - variant_m3u8 = m3u8.loads(m3u8String) - if variant_m3u8.is_variant and (variant_m3u8.version is None or variant_m3u8.version < 7): - quality = quality_to_enum(qltyopt, len(variant_m3u8.playlists)) - if isCatchup: - tmpurl = variant_m3u8.playlists[quality].uri - if "?" in tmpurl: - uriToUse = uriToUse.split("?")[0].replace(onlyUrl, tmpurl) - else: - uriToUse = uriToUse.replace(onlyUrl, tmpurl.split("?")[0]) - del headers['cookie'] - else: - uriToUse = uriToUse.replace( - onlyUrl, variant_m3u8.playlists[quality].uri) - Script.log(uriToUse, lvl=Script.INFO) - return Listitem().from_dict(**{ - "label": plugin._title, - "art": art, - "callback": uriToUse, - "properties": { - "IsPlayable": True, - "inputstream": "inputstream.adaptive", - 'inputstream.adaptive.stream_selection_type': selectionType, - "inputstream.adaptive.chooser_resolution_secure_max": "4K", - "inputstream.adaptive.stream_headers": urlencode(headers), - "inputstream.adaptive.manifest_type": "hls", - "inputstream.adaptive.license_key": "|" + urlencode(headers) + "|R{SSM}|", + try: + is_helper = inputstreamhelper.Helper("mpd", drm="com.widevine.alpha") + hasIs = is_helper.check_inputstream() + if not hasIs: + return + rjson = { + "channel_id": int(channel_id), + "stream_type": "Seek" } - }) - + isCatchup = False + if showtime and srno: + isCatchup = True + rjson["showtime"] = showtime + rjson["srno"] = srno + rjson["stream_type"] = "Catchup" + rjson["programId"] = programId + rjson["begin"] = begin + rjson["end"] = end + Script.log(str(rjson), lvl=Script.INFO) + headers = getHeaders() + headers['channelid'] = str(channel_id) + headers['srno'] = str( + uuid4()) if "srno" not in rjson else rjson["srno"] + res = urlquick.post(GET_CHANNEL_URL, json=rjson, + headers=getChannelHeaders(), max_age=-1, raise_for_status=True) + # if res.status_code + resp = res.json() + art = {} + onlyUrl = resp.get("result", "").split("?")[0].split('/')[-1] + art["thumb"] = art["icon"] = IMG_CATCHUP + \ + onlyUrl.replace(".m3u8", ".png") + cookie = "__hdnea__"+resp.get("result", "").split("__hdnea__")[-1] + headers['cookie'] = cookie + uriToUse = resp.get("result", "") + qltyopt = Settings.get_string("quality") + selectionType = "adaptive" + if qltyopt == 'Manual': + selectionType = "ask-quality" + else: + m3u8Headers = {} + m3u8Headers['user-agent'] = headers['user-agent'] + m3u8Headers['cookie'] = cookie + m3u8Res = urlquick.get(uriToUse, headers=m3u8Headers, + max_age=-1, raise_for_status=True) + # Script.notify("m3u8url", m3u8Res.status_code) + m3u8String = m3u8Res.text + variant_m3u8 = m3u8.loads(m3u8String) + if variant_m3u8.is_variant and (variant_m3u8.version is None or variant_m3u8.version < 7): + quality = quality_to_enum(qltyopt, len(variant_m3u8.playlists)) + if isCatchup: + tmpurl = variant_m3u8.playlists[quality].uri + if "?" in tmpurl: + uriToUse = uriToUse.split( + "?")[0].replace(onlyUrl, tmpurl) + else: + uriToUse = uriToUse.replace( + onlyUrl, tmpurl.split("?")[0]) + del headers['cookie'] + else: + uriToUse = uriToUse.replace( + onlyUrl, variant_m3u8.playlists[quality].uri) + Script.log(uriToUse, lvl=Script.INFO) + return Listitem().from_dict(**{ + "label": plugin._title, + "art": art, + "callback": uriToUse, + "properties": { + "IsPlayable": True, + "inputstream": "inputstream.adaptive", + 'inputstream.adaptive.stream_selection_type': selectionType, + "inputstream.adaptive.chooser_resolution_secure_max": "4K", + "inputstream.adaptive.manifest_headers": urlencode(headers), + "inputstream.adaptive.manifest_type": "hls", + "inputstream.adaptive.license_key": "|" + urlencode(headers) + "|R{SSM}|", + } + }) + except Exception as e: + Script.notify("Login Error", "Session expired. Please login again") + executebuiltin( + "RunPlugin(plugin://plugin.video.jiotv/resources/lib/main/login/)") + return False # Login `route` to access from Settings @Script.register @@ -453,9 +454,8 @@ def logout(plugin): # M3u Generate `route` @Script.register def m3ugen(plugin, notify="yes"): - channels = urlquick.get(CHANNELS_SRC).json().get("result") - r = urlquick.get(DICTIONARY_URL).text.encode('utf8')[3:].decode('utf8') - dictionary = json.loads(r) + channels = getCachedChannels() + dictionary = getCachedDictionary() GENRE_MAP = dictionary.get("channelCategoryMapping") LANG_MAP = dictionary.get("languageIdMapping") @@ -579,14 +579,17 @@ def set_setting(id, value): set_setting("m3uPath", M3U_SRC) set_setting("epgPathType", "1") set_setting("epgUrl", EPG_SRC) + set_setting("epgCache", "false") + set_setting("useInputstreamAdaptiveforHls", "true") set_setting("catchupEnabled", "true") set_setting("catchupWatchEpgBeginBufferMins", "0") set_setting("catchupWatchEpgEndBufferMins", "0") - _setup(M3U_SRC, EPG_PATH) + _setup(M3U_SRC, EPG_SRC) # Cache cleanup @Script.register def cleanup(plugin): urlquick.cache_cleanup(-1) + cleanLocalCache() Script.notify("Cache Cleaned", "") diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 8d49273..c2ec535 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -22,6 +22,8 @@ import socket import json +from resources.lib.constants import CHANNELS_SRC, DICTIONARY_URL, FEATURED_SRC + def get_local_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -42,7 +44,7 @@ def isLoggedIn(func): """ @wraps(func) def login_wrapper(*args, **kwargs): - with PersistentDict("headers") as db: + with PersistentDict("localdb") as db: username = db.get("username") password = db.get("password") headers = db.get("headers") @@ -127,11 +129,12 @@ def login(username, password, mode="unpw"): "dm": "ZUK ZUK Z1" } headers.update(_CREDS) - with PersistentDict("headers") as db: + with PersistentDict("localdb") as db: db["headers"] = headers # db["exp"] = time.time() + 432000 db["exp"] = time.time() + 31536000 if mode == "unpw": + db["exp"] = time.time() + 432000 db["username"] = username db["password"] = password Script.notify("Login Success", "") @@ -156,16 +159,60 @@ def sendOTPV2(mobile): def logout(): - with PersistentDict("headers") as db: + with PersistentDict("localdb") as db: del db["headers"] Script.notify("You\'ve been logged out", "") def getHeaders(): - with PersistentDict("headers") as db: + with PersistentDict("localdb") as db: return db.get("headers", False) +def getCachedChannels(): + with PersistentDict("localdb") as db: + channelList = db.get("channelList", False) + if not channelList: + try: + channelListResp = urlquick.get(CHANNELS_SRC).json().get("result") + db["channelList"] = channelListResp + except: + Script.notify("Connection error ", "Retry after sometime") + return db.get("channelList", False) + +def getCachedDictionary(): + with PersistentDict("localdb") as db: + dictionary = db.get("dictionary", False) + if not dictionary: + try: + r = urlquick.get(DICTIONARY_URL).text.encode( + 'utf8')[3:].decode('utf8') + db["dictionary"] = json.loads(r) + except: + Script.notify("Connection error ", "Retry after sometime") + return db.get("dictionary", False) + +def getFeatured(): + try: + resp = urlquick.get(FEATURED_SRC, headers={ + "usergroup": "tvYR7NSNn7rymo3F", + "os": "android", + "devicetype": "phone", + "versionCode": "290" + }, max_age=-1).json() + return resp.get("featuredNewData", []) + except: + Script.notify("Connection error ", "Retry after sometime") + +def cleanLocalCache(): + try: + with PersistentDict("localdb") as db: + del db["channelList"] + del db["dictionary"] + except: + Script.notify("Cache", "Cleaned") + + def getChannelHeaders(): headers = getHeaders() return { @@ -344,7 +391,7 @@ def _setup(m3uPath, epgUrl): instance_filepath = os.path.join(addon_path, 'instance-settings-91.xml') kodi_rpc('Addons.SetAddonEnabled', { - 'addonid': ADDON_ID, 'enabled': False}) + 'addonid': ADDON_ID, 'enabled': False}) pDialog.update(10) # newer PVR Simple uses instance settings that can't yet be set via python api @@ -364,16 +411,16 @@ def _setup(m3uPath, epgUrl): safe_copy(file_path, file_path+'.bu', del_src=True) pDialog.update(25) kodi_rpc('Addons.SetAddonEnabled', { - 'addonid': ADDON_ID, 'enabled': True}) + 'addonid': ADDON_ID, 'enabled': True}) # wait for migration to occur while not os.path.exists(os.path.join(addon_path, 'instance-settings-1.xml')): monitor.waitForAbort(1) kodi_rpc('Addons.SetAddonEnabled', { - 'addonid': ADDON_ID, 'enabled': False}) + 'addonid': ADDON_ID, 'enabled': False}) monitor.waitForAbort(1) safe_copy(os.path.join(addon_path, 'instance-settings-1.xml'), - instance_filepath, del_src=True) + instance_filepath, del_src=True) pDialog.update(35) with open(instance_filepath, 'r') as f: data = f.read() @@ -385,11 +432,11 @@ def _setup(m3uPath, epgUrl): safe_copy(os.path.join(addon_path, file), os.path.join( addon_path, file[:-3]), del_src=True) kodi_rpc('Addons.SetAddonEnabled', { - 'addonid': ADDON_ID, 'enabled': True}) + 'addonid': ADDON_ID, 'enabled': True}) pDialog.update(70) else: kodi_rpc('Addons.SetAddonEnabled', { - 'addonid': ADDON_ID, 'enabled': True}) + 'addonid': ADDON_ID, 'enabled': True}) pDialog.update(70) set_kodi_setting('epg.futuredaystodisplay', 7) diff --git a/resources/settings.xml b/resources/settings.xml index cf0aad3..55247b0 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,19 +1,30 @@ - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + + @@ -33,11 +44,12 @@ - - - - - + + + + + + diff --git a/service.py b/service.py index f488943..d490742 100644 --- a/service.py +++ b/service.py @@ -24,9 +24,9 @@ def serveForever(handler): t.setDaemon(True) t.start() -if not Settings.get_boolean("popup"): - xbmcgui.Dialog().ok("JioTV Notification", - "Now you can create your custom playlist from BotAllen Dashboard. [CR]Find out more at [B]https://botallen.com/#dashboard[/B] [CR][CR]If you like this add-on then consider donating from [B]https://botallen.com/#donate[/B] [CR][CR]Github: [B]https://github.com/botallen/repository.botallen[/B] [CR]Discord: [B]https://botallen.com/discord[/B] [CR][CR][I]You can disable this popup from settings[/I]") +# if not Settings.get_boolean("popup"): +# xbmcgui.Dialog().ok("JioTV Notification", +# "Now you can create your custom playlist from BotAllen Dashboard. [CR]Find out more at [B]https://botallen.com/#dashboard[/B] [CR][CR]If you like this add-on then consider donating from [B]https://botallen.com/#donate[/B] [CR][CR]Github: [B]https://github.com/botallen/repository.botallen[/B] [CR]Discord: [B]https://botallen.com/discord[/B] [CR][CR][I]You can disable this popup from settings[/I]") if Settings.get_boolean("m3ugen"): executebuiltin(