# -*- coding: utf-8 -*-
"""
sources.py ---
Copyright (C) 2017, Jen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Update :2019-3-11
Version 2.0 Jen
Updated to include torrent and magnet links in xml
[/COLOR]magnet:?xt=urn:btih:9CA50DA836B52A6BCB06AAA91BB2478FD96F5DD0&dn=A+Beautiful+Mind+%282001%29+1080p+BrRip+x264+-+YIFY
"""
import random
import universalscrapers
import requests
import xbmcaddon
import xbmcgui
import xbmc
import koding
from koding import route
import sys
import xbmcplugin
import resolveurl
from resources.lib.util.xml import JenItem, JenList
from resources.lib.util.messages import get_link_message, get_searching_message
from resources.lib.util.info import get_info
from resources.lib.player import JenPlayer
from resources.lib.plugin import run_hook
from language import get_string as _
ADDON = xbmcaddon.Addon()
DIALOG = xbmcgui.Dialog()
class Sources(object):
"""interface to NaN scraper library and helper functions"""
def __init__(self):
"""initialise class"""
pass
@staticmethod
def get_sources(
title,
year,
imdb,
tvdb,
season,
episode,
tvshowtitle,
premiered,
timeout=30,
preset="search",
dialog=None,
exclude=None,
scraper_title=False,
listitem=None,
output_function=koding.Play_Video,
skip_selector=False,
player=None,
):
"""
scrapes for video sources using NaN scraper library
Args:
title: movie or episode title
year: year movie/episode came out
imdb: imdb identifier
tvdb: tvdb identifier
season: season number
episode: episode number
tvshowtitle: title of tv show
premiered: year tv show premiered
timeout: timeout for scraping link
preset: preferred quality of stream
dialog: dialog to use for displaying messages
exclude: list of scrapers to exclude
scraper_title: extra movie/tv show title to search first.
required if scrapers use an alternate spelling
Returns:
Boolean indicating playback success
"""
year = str(year)
content = "movie" if tvshowtitle is None else "episode"
allow_debrid = ADDON.getSetting("allow_debrid") == "true"
if ADDON.getSetting("use_link_dialog") == "true" and not skip_selector:
# use link selector
if content == "movie":
scraper = universalscrapers.scrape_movie_with_dialog
link, rest = scraper(
title,
year,
imdb,
timeout=timeout,
exclude=exclude,
extended=True,
sort_function=Sources.sort_function,
enable_debrid=allow_debrid,
)
elif content == "episode":
scraper = universalscrapers.scrape_episode_with_dialog
link, rest = scraper(
tvshowtitle,
year,
premiered,
season,
episode,
imdb,
tvdb,
timeout=timeout,
exclude=exclude,
extended=True,
sort_function=Sources.sort_function,
enable_debrid=allow_debrid,
)
else:
return
if type(link) == dict and "path" in link:
link = link["path"]
if link is None:
return False
url = link["url"]
if ADDON.getSetting("link_fallthrough") == "true":
played = False
index = 0
links = []
for item in rest:
if type(item) == dict and "path" in item:
links.extend(item["path"][1])
else:
links.extend(item[1])
index = links.index(link)
links = links[index + 1 :]
num_results = len(rest) + 1
while not played:
try:
if dialog is not None and dialog.iscanceled():
return False
if dialog is not None:
index = index + 1
percent = int((index * 100) / num_results)
line = "%s - %s (%s)" % (
link["scraper"],
link["source"],
link["quality"],
)
dialog.update(percent, line)
except:
pass
try:
resolved_link = universalscrapers.resolve(
link["scraper"], link["url"]
)
played = output_function(
resolved_link,
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
link = links[0]
links = links[1:]
except:
return False
return played
else:
resolved_link = universalscrapers.resolve(link["scraper"], link["url"])
return output_function(
resolved_link,
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
else:
if content == "movie":
title = title
scraper = universalscrapers.scrape_movie
links_scraper = scraper(
title,
year,
imdb,
timeout=timeout,
exclude=exclude,
enable_debrid=allow_debrid,
)
elif content == "episode":
if scraper_title:
tvshowtitle = title
tvshowtitle = tvshowtitle
scraper = universalscrapers.scrape_episode
links_scraper = scraper(
tvshowtitle,
year,
premiered,
season,
episode,
imdb,
tvdb,
timeout=timeout,
exclude=exclude,
enable_debrid=allow_debrid,
)
else:
return
sd_links = []
non_direct_links = []
non_direct_sd_links = []
num_scrapers = len(universalscrapers.relevant_scrapers())
index = 0
try:
for scraper_links in links_scraper():
if dialog is not None and dialog.iscanceled():
return
if dialog is not None:
index = index + 1
percent = int((index * 100) / num_scrapers)
dialog.update(percent)
if scraper_links is not None:
random.shuffle(scraper_links)
for scraper_link in scraper_links:
if dialog is not None and dialog.iscanceled():
return False
if Sources().__check_skip_pairing(scraper_link):
continue
quality = Sources.__determine_quality(scraper_link["quality"])
preset = preset.lower()
if preset == "searchsd":
if quality == "HD":
continue
elif preset == "search":
if quality == "SD":
sd_links.append(scraper_link)
if scraper_link["direct"]:
resolved_link = universalscrapers.resolve(scraper_link["scraper"], scraper_link["url"])
result = output_function(
resolved_link,
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
if result:
return result
else:
non_direct_links.append(scraper_link)
for scraper_link in non_direct_links:
if dialog is not None and dialog.iscanceled():
return False
resolved_link = universalscrapers.resolve(scraper_link["scraper"], scraper_link["url"])
result = output_function(
resolved_link,
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
if result:
return result
for scraper_link in sd_links:
if dialog is not None and dialog.iscanceled():
return
if scraper_link["direct"]:
resolved_link = universalscrapers.resolve(scraper_link["scraper"], scraper_link["url"])
result = output_function(
resolved_link,
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
if result:
return result
else:
non_direct_sd_links.append(scraper_link)
for scraper_link in non_direct_sd_links:
if dialog is not None and dialog.iscanceled():
return
resolved_link = universalscrapers.resolve(scraper_link["scraper"], scraper_link["url"])
result = output_function(
resolved_link,
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
if result:
return result
return False
except:
return False
@staticmethod
def get_music_sources(
title,
artist,
timeout=30,
preset="search",
dialog=None,
exclude=None,
listitem=None,
output_function=koding.Play_Video,
skip_selector=False,
player=None,
):
"""
scrapes for music sources using NaN scraper library
Args:
title: song title
artist: song artist
timeout: timeout for scraping link
preset: preferred quality of stream
dialog: dialog to use for displaying messages
exclude: list of scrapers to exclude
Returns:
Boolean indicating playback success
"""
title = title
allow_debrid = ADDON.getSetting("allow_debrid") == "true"
if ADDON.getSetting("use_link_dialog") == "true" and not skip_selector:
link, rest = universalscrapers.scrape_song_with_dialog(
title,
artist,
timeout=timeout,
exclude=exclude,
enable_debrid=allow_debrid,
extended=True,
)
if type(link) == dict and "path" in link:
link = link["path"]
if link is None:
return False
url = link["url"]
if ADDON.getSetting("link_fallthrough") == "true":
played = False
index = 0
links = []
for item in rest:
if type(item) == dict and "path" in item:
links.extend(item["path"][1])
else:
links.extend(item[1])
index = links.index(link)
links = links[index + 1 :]
num_results = len(rest) + 1
while not played:
try:
if dialog is not None and dialog.iscanceled():
return
if dialog is not None:
index = index + 1
percent = int((index * 100) / num_results)
line = "%s - %s (%s)" % (
link["scraper"],
link["source"],
link["quality"],
)
dialog.update(percent, line)
except:
pass
try:
played = output_function(
url,
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
link = links[0]
links = links[1:]
except:
return False
return played
else:
return output_function(
url,
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
links_scraper = universalscrapers.scrape_song(
title, artist, timeout=timeout, exclude=exclude, enable_debrid=allow_debrid
)
sd_links = []
num_scrapers = len(universalscrapers.relevant_scrapers())
index = 0
try:
for scraper_links in links_scraper():
if dialog is not None and dialog.iscanceled():
return
if dialog is not None:
index = index + 1
percent = int((index * 100) / num_scrapers)
dialog.update(percent)
if scraper_links is not None:
random.shuffle(scraper_links)
for scraper_link in scraper_links:
if dialog is not None and dialog.iscanceled():
return
if Sources().__check_skip_pairing(scraper_link):
continue
quality = Sources.__determine_quality(scraper_link["quality"])
preset = preset.lower()
if preset == "searchsd":
if quality == "HD":
continue
elif preset == "search":
if quality == "SD":
sd_links.append(scraper_link)
result = output_function(
scraper_link["url"],
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
if result:
return result
for scraper_link in sd_links:
if dialog is not None and dialog.iscanceled():
return
result = output_function(
scraper_link["url"],
showbusy=False,
ignore_dp=True,
item=listitem,
player=player,
resolver=resolveurl,
)
if result:
return result
except:
pass
return False
@staticmethod
def youtube_resolve(url):
"""
transform youtube url to link to youtube add-on
Args:
url: youtube url
Returns:
playable url
"""
try:
youtube_id = url.split("?v=")[-1].split("/")[-1].split("?")[0].split("&")[0]
result = requests.head("http://www.youtube.com/watch?v=%s" % youtube_id)
if result:
return "plugin://plugin.video.youtube/play/?video_id=%s" % (youtube_id)
except:
return
@staticmethod
def sort_function(item):
"""
transform items quality into a string that's sort-able
Args:
item: scraper link
Returns:
sortable quality string
"""
if "quality" in item[1][0]:
quality = item[1][0]["quality"]
else:
quality = item[1][0]["path"]["quality"]
if "path" in item[1][0]:
if "debridonly" in item[1][0]["path"]:
q = "A"
elif "debridonly" in item[1][0]:
q = "A"
else:
q = "B"
if q == "A":
if quality.startswith("4K"):
quality = "Aa"
elif quality.startswith("1080"):
quality = "Ab"
elif quality.startswith("720"):
quality = "Ac"
elif quality.startswith("560"):
quality = "Ad"
elif quality == "DVD":
quality = "Ae"
elif quality == "HD":
quality = "Af"
elif quality.startswith("480"):
quality = "Ba"
elif quality.startswith("360"):
quality = "Bb"
elif quality.startswith("SD"):
quality = "Bc"
else:
quality = "CZ"
elif quality.startswith("4K"):
quality = "HDa"
elif quality.startswith("1080"):
quality = "HDb"
elif quality.startswith("720"):
quality = "HDc"
elif quality.startswith("560"):
quality = "HDd"
elif quality == "DVD":
quality = "HDe"
elif quality == "HD":
quality = "HDf"
elif quality.startswith("480"):
quality = "SDa"
elif quality.startswith("360"):
quality = "SDb"
elif quality.startswith("SD"):
quality = "SDc"
else:
quality = "Z"
return quality
@staticmethod
def __determine_quality(quality_string):
try:
quality = int(quality_string)
if quality > 576:
return "HD"
else:
return "SD"
except ValueError:
if quality_string not in ["SD", "CAM", "SCR"]:
return "HD"
else:
return "SD"
@staticmethod
def __check_skip_pairing(scraper_link):
if (
not ADDON.getSetting("allow_openload") == "true"
and "openload" in scraper_link["url"]
):
return True
if (
not ADDON.getSetting("allow_the_video_me") == "true"
and "thevideo.me" in scraper_link["url"]
):
return True
if (
not ADDON.getSetting("allow_the_vidup_me") == "true"
and "vidup.me" in scraper_link["url"]
):
return True
return False
def choose_quality(link, name=None, selected_link=None):
"""
choose quality for scraping
Keyword Arguments:
link -- Jenitem link with sublinks
name -- Name to display in dialog (default None)
"""
import re
if name is None:
name = xbmc.getInfoLabel("listitem.label")
if link.startswith("http") or link.startswith("plugin"):
sublinks = [link]
else:
jen_link = JenItem(link)
sublinks = jen_link.getAll("sublink")
if not sublinks:
sublinks = [jen_link]
links = []
message = get_link_message()
if selected_link is None:
default_link = ADDON.getSetting("default_link")
else:
default_link = selected_link
link_dialog = ADDON.getSetting("use_link_dialog") == "true"
direct_links = False
for sublink in sublinks:
if link_dialog and "search" in sublink:
continue
if "searchsd" in sublink:
if default_link == "SD":
return sublink
label = "SD"
if message["SD"] != "":
label += " (%s)" % message["SD"]
new_item = (label, sublink)
elif "search" in sublink:
if default_link == "HD":
return sublink
label = "HD"
if message["HD"] != "":
label += " (%s)" % message["HD"]
new_item = (label, sublink)
else:
direct_links = True
match = re.findall("(.*?)\((.*?)\)", sublink)
if match:
new_item = ("%s" % match[0][1], match[0][0])
else:
new_item = ("Link %s" % (int(sublinks.index(sublink)) + 1), sublink)
links.append(new_item)
if link_dialog and (not direct_links or len(sublinks) > 1):
links.append(("Search", "search"))
if len(links) == 1:
url = links[0][1]
return url
select = xbmcgui.Dialog().select(name, [i[0] for i in links])
if select == -1:
return False
else:
url = links[select][1]
return url
@route(mode="get_sources", args=["url"])
def get_sources(item):
"""
get video_link and try to play it
Keyword Arguments:
item -- JenItem to try playing
"""
result = run_hook("get_sources", item)
if result:
return
if item.startswith(""):
# link to plugin
link = JenItem(item)["link"]
sublinks = JenItem(link).getAll("sublink")
if sublinks:
if len(sublinks) > 1:
link = choose_quality(link)
else:
link = sublinks[0]
link = link.replace("&", "&")
xbmc.executebuiltin("Container.update(" + link + ")")
return
item = JenItem(item)
link = item["link"]
if not link or link.replace("\t", "") == "":
return
meta = JenItem(item["meta"])
title = meta["title"]
year = meta.get("year", "").split("-")[0].strip()
imdb = meta.get("imdb", "")
tvdb = meta.get("tvdb", "")
season = meta.get("season", "")
episode = meta.get("episode", "")
tvshowtitle = meta.get("tvshowtitle", None)
premiered = meta.get("premiered", "")
try:
premiered = premiered.split("-")[0].strip()
except:
if len(premiered) == 4:
pass
elif not premiered:
pass
else:
koding.dolog("wrong premiered format")
busy_dialog = xbmcgui.DialogProgress()
dialog = xbmcgui.Dialog()
icon = ADDON.getAddonInfo("icon")
jenplayer = JenPlayer(resume=False)
try:
spec = {"identifier": imdb, "season": season or "0", "episode": episode or "0"}
match = koding.Get_From_Table("watched", spec)
if match:
match = match[0]
if match["currentTime"] and not match["currentTime"] == "0":
if dialog.yesno(
ADDON.getAddonInfo("name"),
_("Previous playback detected"),
yeslabel=_("Resume"),
nolabel=_("Restart"),
):
jenplayer = JenPlayer(resume=True)
except:
pass
jenplayer.setItem(item)
busy_dialog.create(xbmcaddon.Addon().getAddonInfo("name"), _("Processing Link"))
preset = choose_quality(link)
message = get_searching_message(preset)
played = False
infolabels = {}
if preset:
preset = preset.replace("&", "&")
busy_dialog.update(0, message)
listitem = None
fetch_meta = ADDON.getSetting("metadata") == "true"
listitem = xbmcgui.ListItem(
path=link,
iconImage=item.get("thumbnail", icon),
thumbnailImage=item.get("thumbnail", icon),
)
infolabels = {}
if fetch_meta and imdb != "0": # only try valid items with imdb
try:
info, created = get_info([item.item_string])
if info and type(info) == dict:
infolabels = info
except:
pass
else:
infolabels["title"] = title
infolabels["name"] = title
if "plotoutline" not in infolabels:
infolabels["plotoutline"] = infolabels.get("plot", "")
if item.get("content", "") == "song":
listitem.setInfo(
type="Music",
infoLabels={
"title": meta.get("title", ""),
"artist": meta.get("artist", ""),
},
)
else:
listitem.setInfo(type="video", infoLabels=infolabels)
listitem.setLabel(item.get("title", item.get("name", "")))
if "search" in preset:
exclude_scrapers_content = item.get("exclude_scrapers", "")
if exclude_scrapers_content:
exclude_scrapers = exclude_scrapers_content.split(";")
else:
exclude_scrapers = None
# nanscraper link
if item.get("content", "") == "song":
artist = item.get("artist", "")
played = Sources.get_music_sources(
title,
artist,
preset=preset,
dialog=busy_dialog,
exclude=exclude_scrapers,
listitem=listitem,
player=jenplayer,
)
else:
played = Sources.get_sources(
title,
year,
imdb,
tvdb,
season,
episode,
tvshowtitle,
premiered,
preset=preset,
dialog=busy_dialog,
listitem=listitem,
exclude=exclude_scrapers,
player=jenplayer,
)
elif preset.startswith("http") or preset.startswith("plugin") or preset.endswith('.torrent') or preset.startswith('magnet'):
# direct link
if "/playlist" in preset and "youtube" in preset:
busy_dialog.close()
xbmc.executebuiltin("Container.update(" + preset + ")")
return
elif "plugin://plugin.video.youtube/play/?video_id=" in preset:
xbmc.executebuiltin("PlayMedia(%s)" % preset)
played = True
elif item["content"] == "image":
busy_dialog.close()
xbmc.executebuiltin("ShowPicture(%s)" % preset)
played = True
else:
played = koding.Play_Video(
preset,
showbusy=False,
ignore_dp=True,
item=listitem,
player=jenplayer,
resolver=resolveurl,
)
else:
# who knows
busy_dialog.close()
koding.dolog("unknown link type: " + repr(preset))
raise Exception()
busy_dialog.close()
if played:
jenplayer.keep_alive()
@route(mode="queue", args=["url"])
def queue_source(item, depth=0):
"""
queue item
Keyword Arguments:
item -- JenItem to try playing
"""
from resources.lib.util.url import get_addon_url
jen_item = JenItem(item)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
if "" in str(jen_item):
play = False
if xbmcaddon.Addon().getSetting("autostart_queue") == "true":
if playlist.size() == 0:
play = True
playlist.add(
get_addon_url("get_sources", str(item)),
xbmcgui.ListItem(
jen_item["title"], iconImage=jen_item.get("thumbnail", "")
),
)
if play:
play_queue()
else:
link = jen_item.get("url", jen_item.get("link", ""))
jenlist = JenList(link).get_raw_list()
for list_item in jenlist:
queue_source(str(list_item), depth + 1)
if depth == 0:
xbmcgui.Dialog().notification(
ADDON.getAddonInfo("name"),
_("Finished Queueing").encode("utf-8"),
ADDON.getAddonInfo("icon"),
)
xbmc.executebuiltin("Container.Refresh")
@route(mode="clear_queue")
def clear_queue():
xbmc.PlayList(xbmc.PLAYLIST_VIDEO).clear()
xbmcgui.Dialog().notification(
ADDON.getAddonInfo("name"),
_("Queue cleared").encode("utf-8"),
ADDON.getAddonInfo("icon"),
)
xbmc.executebuiltin("Container.Refresh")
@route(mode="play_queue")
def play_queue():
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
if playlist.size() > 0:
item = playlist[0]
xbmc.Player().play(playlist, item)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, item)
else:
xbmcgui.Dialog().notification(
ADDON.getAddonInfo("name"),
_("Queue is empty").encode("utf-8"),
ADDON.getAddonInfo("icon"),
)