KoD 1.6
- rimosso supporto a TVDB (l'accesso alle API diventerà a pagamento) - aggiunto canale Discovery+ - aggiunta possibilità di scegliere numerazioni alternative per le serie tv - migliorie interne di vario tipo (tra cui un migliore riconoscimento dei contenuti nel caso siano scritti male)
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
name: Test Suite
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '00 15 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
export KODI_INTERACTIVE=0
|
||||||
|
./tests/run.sh
|
||||||
|
|
||||||
|
- name: Commit & Push changes
|
||||||
|
uses: dmnemec/copy_file_to_another_repo_action@v1.0.4
|
||||||
|
env:
|
||||||
|
API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }}
|
||||||
|
with:
|
||||||
|
source_file: 'reports'
|
||||||
|
destination_repo: 'kodiondemand/kodiondemand.github.io'
|
||||||
|
user_email: 'tests@kod.bot'
|
||||||
|
user_name: 'bot'
|
||||||
|
commit_message: 'Test suite'
|
||||||
@@ -14,11 +14,13 @@ jobs:
|
|||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: 2.7
|
python-version: 3.7
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install requests
|
||||||
|
|
||||||
- name: Update domains
|
- name: Update domains
|
||||||
run: |
|
run: python tools/updateDomains.py
|
||||||
python tools/updateDomains.py
|
|
||||||
|
|
||||||
- name: Commit & Push changes
|
- name: Commit & Push changes
|
||||||
uses: actions-js/push@master
|
uses: actions-js/push@master
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.5.3" provider-name="KoD Team">
|
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.6" provider-name="KoD Team">
|
||||||
<requires>
|
<requires>
|
||||||
<!-- <import addon="script.module.libtorrent" optional="true"/> -->
|
<!-- <import addon="script.module.libtorrent" optional="true"/> -->
|
||||||
<import addon="metadata.themoviedb.org"/>
|
<import addon="metadata.themoviedb.org"/>
|
||||||
<import addon="metadata.tvdb.com"/>
|
<import addon="metadata.tvshows.themoviedb.org"/>
|
||||||
|
<!-- <import addon="metadata.tvdb.com"/> -->
|
||||||
|
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.pluginsource" library="default.py">
|
<extension point="xbmc.python.pluginsource" library="default.py">
|
||||||
@@ -26,9 +27,10 @@
|
|||||||
<screenshot>resources/media/themes/ss/2.png</screenshot>
|
<screenshot>resources/media/themes/ss/2.png</screenshot>
|
||||||
<screenshot>resources/media/themes/ss/3.png</screenshot>
|
<screenshot>resources/media/themes/ss/3.png</screenshot>
|
||||||
</assets>
|
</assets>
|
||||||
<news>- correzioni di alcuni bug (citiamo ad esempio il crash con il refresh rate e l'impossibilita di entrare nel menu server bloccati)
|
<news>- rimosso supporto a TVDB (l'accesso alle API diventerà a pagamento)
|
||||||
- fix per cambio di struttura a qualche canale/server
|
- aggiunto canale Discovery+
|
||||||
- migliorie interne</news>
|
- aggiunta possibilità di scegliere numerazioni alternative per le serie tv
|
||||||
|
- migliorie interne di vario tipo (tra cui un migliore riconoscimento dei contenuti nel caso siano scritti male)</news>
|
||||||
<description lang="it">Naviga velocemente sul web e guarda i contenuti presenti</description>
|
<description lang="it">Naviga velocemente sul web e guarda i contenuti presenti</description>
|
||||||
<disclaimer>[COLOR red]The owners and submitters to this addon do not host or distribute any of the content displayed by these addons nor do they have any affiliation with the content providers.[/COLOR]
|
<disclaimer>[COLOR red]The owners and submitters to this addon do not host or distribute any of the content displayed by these addons nor do they have any affiliation with the content providers.[/COLOR]
|
||||||
[COLOR yellow]Kodi © is a registered trademark of the XBMC Foundation. We are not connected to or in any other way affiliated with Kodi, Team Kodi, or the XBMC Foundation. Furthermore, any software, addons, or products offered by us will receive no support in official Kodi channels, including the Kodi forums and various social networks.[/COLOR]</disclaimer>
|
[COLOR yellow]Kodi © is a registered trademark of the XBMC Foundation. We are not connected to or in any other way affiliated with Kodi, Team Kodi, or the XBMC Foundation. Furthermore, any software, addons, or products offered by us will receive no support in official Kodi channels, including the Kodi forums and various social networks.[/COLOR]</disclaimer>
|
||||||
|
|||||||
+13
-13
@@ -1,44 +1,44 @@
|
|||||||
{
|
{
|
||||||
"direct": {
|
"direct": {
|
||||||
"altadefinizione01_link": "https://altadefinizione01.fitness",
|
"altadefinizione01_link": "https://altadefinizione01.market",
|
||||||
"animealtadefinizione": "https://www.animealtadefinizione.it",
|
"animealtadefinizione": "https://www.animealtadefinizione.it",
|
||||||
"animeforce": "https://www.animeforce.it",
|
"animeforce": "https://www.animeforce.it",
|
||||||
"animeleggendari": "https://animeora.com",
|
"animeleggendari": "https://www.animebig.xyz",
|
||||||
"animesaturn": "https://www.animesaturn.it",
|
"animesaturn": "https://www.animesaturn.it",
|
||||||
"animestream": "https://www.animeworld.tv",
|
"animestream": "https://www.animeworld.tv",
|
||||||
"animesubita": "http://www.animesubita.org",
|
"animesubita": "http://www.animesubita.org",
|
||||||
"animetubeita": "http://www.animetubeita.com",
|
"animetubeita": "http://www.animetubeita.com",
|
||||||
"animeunity": "https://www.animeunity.it",
|
"animeunity": "https://www.animeunity.it",
|
||||||
"animeuniverse": "https://www.animeuniverse.it/",
|
"animeuniverse": "https://www.animeuniverse.it",
|
||||||
"animeworld": "https://www.animeworld.tv",
|
"animeworld": "https://www.animeworld.tv",
|
||||||
"casacinema": "https://www.casacinema.page",
|
"casacinema": "https://www.casacinema.page",
|
||||||
"cb01anime": "https://www.cineblog01.red",
|
"cb01anime": "https://www.cineblog01.red",
|
||||||
"cinemalibero": "https://cinemalibero.store",
|
"cinemalibero": "https://cinemalibero.win",
|
||||||
"cinetecadibologna": "http://cinestore.cinetecadibologna.it",
|
"cinetecadibologna": "http://cinestore.cinetecadibologna.it",
|
||||||
|
"discoveryplus": "https://www.discoveryplus.com",
|
||||||
"dreamsub": "https://dreamsub.stream",
|
"dreamsub": "https://dreamsub.stream",
|
||||||
"dsda": "https://www.dsda.press",
|
"dsda": "https://www.dsda.press",
|
||||||
"eurostreaming": "https://eurostreaming.chat",
|
"eurostreaming": "https://eurostreaming.tube",
|
||||||
"fastsubita": "https://fastsubita.xyz",
|
|
||||||
"filmgratis": "https://www.filmaltadefinizione.me",
|
"filmgratis": "https://www.filmaltadefinizione.me",
|
||||||
"filmigratis": "https://filmigratis.org",
|
"filmigratis": "https://filmigratis.org",
|
||||||
"filmsenzalimiticc": "https://www.filmsenzalimiti01.online",
|
"filmsenzalimiticc": "https://www.filmsenzalimiti01.work",
|
||||||
"filmstreaming01": "https://filmstreaming01.com",
|
"filmstreaming01": "https://filmstreaming01.com",
|
||||||
"guardaserie_stream": "https://guardaserie.host",
|
"guardaserie_stream": "https://guardaserie.host",
|
||||||
"guardaseriecam": "https://guardaserie.cam",
|
"guardaseriecam": "https://guardaserie.cam",
|
||||||
"guardaserieclick": "https://www.guardaserie.directory",
|
"guardaserieclick": "https://www.guardaserie.kim",
|
||||||
"guardaserieicu": "https://guardaserie.shop",
|
"guardaserieicu": "https://guardaserie.shop",
|
||||||
"hd4me": "https://hd4me.net",
|
"hd4me": "https://hd4me.net",
|
||||||
"ilcorsaronero": "https://ilcorsaronero.link",
|
"ilcorsaronero": "https://ilcorsaronero.link",
|
||||||
"ilgeniodellostreaming": "https://ilgeniodellostreaming.cat",
|
"ilgeniodellostreaming": "https://ilgeniodellostreaming.pub",
|
||||||
"ilgeniodellostreaming_cam": "https://ilgeniodellostreaming.gold",
|
"ilgeniodellostreaming_cam": "https://ilgeniodellostreaming.gold",
|
||||||
"italiaserie": "https://italiaserie.top",
|
"italiaserie": "https://italiaserie.bid",
|
||||||
"mediasetplay": "https://www.mediasetplay.mediaset.it",
|
"mediasetplay": "https://www.mediasetplay.mediaset.it",
|
||||||
"mondoserietv": "https://mondoserietv.fun",
|
"mondoserietv": "https://mondoserietv.fun",
|
||||||
"paramount": "https://www.paramountnetwork.it",
|
"paramount": "https://www.paramountnetwork.it",
|
||||||
"piratestreaming": "https://www.piratestreaming.camp",
|
"piratestreaming": "https://www.piratestreaming.live",
|
||||||
"polpotv": "https://roma.polpo.tv",
|
"polpotv": "https://roma.polpo.tv",
|
||||||
"raiplay": "https://www.raiplay.it",
|
"raiplay": "https://www.raiplay.it",
|
||||||
"serietvonline": "https://serietvonline.cam",
|
"serietvonline": "https://serietvonline.website",
|
||||||
"serietvsubita": "http://serietvsubita.xyz",
|
"serietvsubita": "http://serietvsubita.xyz",
|
||||||
"serietvu": "https://www.serietvu.link",
|
"serietvu": "https://www.serietvu.link",
|
||||||
"streamingcommunity": "https://streamingcommunity.net",
|
"streamingcommunity": "https://streamingcommunity.net",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"cineblog01": "https://cb01.uno",
|
"cineblog01": "https://cb01.uno",
|
||||||
"film4k": "https://film4k-nuovo.link",
|
"film4k": "https://film4k-nuovo.link",
|
||||||
"filmpertutti": "https://filmpertutti.nuovo.live",
|
"filmpertutti": "https://filmpertutti.nuovo.live",
|
||||||
"seriehd": "https://nuovoindirizzo.info/seriehd/",
|
"seriehd": "https://nuovoindirizzo.info/seriehd",
|
||||||
"tantifilm": "https://www.tantifilm.wiki"
|
"tantifilm": "https://www.tantifilm.wiki"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ from platformcode import config
|
|||||||
|
|
||||||
def findhost(url):
|
def findhost(url):
|
||||||
data = support.httptools.downloadpage(url).data
|
data = support.httptools.downloadpage(url).data
|
||||||
host = support.scrapertools.find_single_match(data, '<div class="elementor-button-wrapper"> <a href="([^"]+)"')
|
host = support.scrapertools.find_single_match(data, '<div class="elementor-button-wrapper">\s*<a href="([^"]+)"')
|
||||||
return host
|
return host
|
||||||
|
|
||||||
host = config.get_channel_url(findhost)
|
host = config.get_channel_url(findhost)
|
||||||
@@ -72,6 +72,7 @@ def peliculas(item):
|
|||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def genres(item):
|
def genres(item):
|
||||||
|
# debugBlock=True
|
||||||
action = 'peliculas'
|
action = 'peliculas'
|
||||||
patronMenu = r'<li><a href="(?P<url>[^"]+)">(?P<title>[^<]+)<'
|
patronMenu = r'<li><a href="(?P<url>[^"]+)">(?P<title>[^<]+)<'
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ def genres(item):
|
|||||||
elif item.args == 'years':
|
elif item.args == 'years':
|
||||||
patronBlock = r'<ul class="listSubCat" id="Anno">(?P<block>.*)<ul class="listSubCat" id="Qualita">'
|
patronBlock = r'<ul class="listSubCat" id="Anno">(?P<block>.*)<ul class="listSubCat" id="Qualita">'
|
||||||
elif item.args == 'quality':
|
elif item.args == 'quality':
|
||||||
patronBlock = r'<ul class="listSubCat" id="Qualita">(?P<block>.*)</li> </ul> </div> </div> </div> <a'
|
patronBlock = r'<ul class="listSubCat" id="Qualita">(?P<block>.*)</li>\s*?</ul>\s*?</div>\s*?</div>\s*?</div>\s*?<a'
|
||||||
elif item.args == 'lucky': # sono i titoli random nella pagina
|
elif item.args == 'lucky': # sono i titoli random nella pagina
|
||||||
patronBlock = r'<h3 class="titleSidebox dado">FILM RANDOM</h3>(?P<block>.*)</section>'
|
patronBlock = r'<h3 class="titleSidebox dado">FILM RANDOM</h3>(?P<block>.*)</section>'
|
||||||
patron = r'<li><a href="(?P<url>[^"]+)">(?P<title>[^<[]+)(?:\[(?P<lang>.+?)\])?<'
|
patron = r'<li><a href="(?P<url>[^"]+)">(?P<title>[^<[]+)(?:\[(?P<lang>.+?)\])?<'
|
||||||
|
|||||||
@@ -104,11 +104,11 @@ def peliculas(item):
|
|||||||
action = 'select'
|
action = 'select'
|
||||||
|
|
||||||
if item.args == 'newest':
|
if item.args == 'newest':
|
||||||
patron = r'<li><a href="(?P<url>[^"]+)"[^=]+="(?P<thumb>[^"]+)"><div>\s+<div[^>]+>(?P<title>[^\(\[<]+)(?:\[(?P<quality1>HD)\])?[ ]?(?:\(|\[)?(?P<lang>Sub-ITA)?(?:\)|\])?[ ]?(?:\[(?P<quality>.+?)\])?[ ]?(?:\((?P<year>\d+)\))?<(?:[^>]+>.+?(?:title="Nuovi episodi">(?P<episode>\d+x\d+)[ ]?(?P<lang2>Sub-Ita)?|title="IMDb">(?P<rating>[^<]+)))?'
|
patron = r'<li><a href="(?P<url>[^"]+)"[^=]+="(?P<thumb>[^"]+)"><div>\s*?<div[^>]+>(?P<title>[^\(\[<]+)(?:\[(?P<quality1>HD)\])?[ ]?(?:\(|\[)?(?P<lang>Sub-ITA)?(?:\)|\])?[ ]?(?:\[(?P<quality>.+?)\])?[ ]?(?:\((?P<year>\d+)\))?<(?:[^>]+>.+?(?:title="Nuovi episodi">(?P<episode>\d+x\d+)[ ]?(?P<lang2>Sub-Ita)?|title="IMDb">(?P<rating>[^<]+)))?'
|
||||||
else:
|
else:
|
||||||
patron = r'<li><a href="(?P<url>[^"]+)"[^=]+="(?P<thumb>[^"]+)"><div>\s+<div[^>]+>(?P<title>[^\(\[<]+)(?:\[(?P<quality1>HD)\])?[ ]?(?:\(|\[)?(?P<lang>Sub-ITA)?(?:\)|\])?[ ]?(?:\[(?P<quality>.+?)\])?[ ]?(?:\((?P<year>\d+)\))?'
|
patron = r'<li><a href="(?P<url>[^"]+)"[^=]+="(?P<thumb>[^"]+)"><div>\s*?<div[^>]+>(?P<title>[^\(\[<]+)(?:\[(?P<quality1>HD)\])?\s?(?:[\(\[])?(?P<lang>Sub-ITA)?(?:[\)\]])?\s?(?:\[(?P<quality>.+?)\])?\s?(?:\((?P<year>\d+)\))?<'
|
||||||
|
|
||||||
patronNext = r'<a href="([^"]+)" >Pagina'
|
patronNext = r'<a href="([^"]+)"\s*>Pagina'
|
||||||
# debug = True
|
# debug = True
|
||||||
|
|
||||||
def itemHook(item):
|
def itemHook(item):
|
||||||
|
|||||||
+11
-3
@@ -37,7 +37,14 @@ def menu(item):
|
|||||||
def search(item, texto):
|
def search(item, texto):
|
||||||
support.info(texto)
|
support.info(texto)
|
||||||
item.url = host + "/?s=" + texto
|
item.url = host + "/?s=" + texto
|
||||||
return peliculas(item)
|
try:
|
||||||
|
return peliculas(item)
|
||||||
|
except:
|
||||||
|
import sys
|
||||||
|
for line in sys.exc_info():
|
||||||
|
support.info('search log:', line)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def newest(categoria):
|
def newest(categoria):
|
||||||
support.info(categoria)
|
support.info(categoria)
|
||||||
@@ -59,12 +66,13 @@ def newest(categoria):
|
|||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
|
# debug=True
|
||||||
blacklist = Blacklist
|
blacklist = Blacklist
|
||||||
item.contentType = 'tvshow'
|
item.contentType = 'tvshow'
|
||||||
if item.args == 'newest':
|
if item.args == 'newest':
|
||||||
patron = r'<div id="blockvids">\s*<ul>\s*<li>\s*<a href="(?P<url>[^"]+)"[^>]+><img[^>]+src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^\[]+)\[(?P<lang>[^\]]+)\]'
|
patron = r'<div id="blockvids">\s*<ul>\s*<li>\s*<a href="(?P<url>[^"]+)"[^>]+><img[^>]+src="(?P<thumb>[^"]+)"[^>]*>(?:[^>]+>){4}(?P<title>[^\[]+)\[(?P<lang>[^\]]+)\]'
|
||||||
else:
|
else:
|
||||||
patron = r'<div class="span4">\s*<a href="(?P<url>[^"]+)"><img src="(?P<thumb>[^"]+)"[^>]+><\/a>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+> +<h1>(?P<title>[^<\[]+)(?:\[(?P<lang>[^\]]+)\])?</h1></a>.*?-->(?:.*?<br />)?\s*(?P<plot>[^<]+)'
|
patron = r'<div class="span4">\s*<a href="(?P<url>[^"]+)"><img src="(?P<thumb>[^"]+)"[^>]+><\/a>(?:[^>]+>){7}\s*<h1>(?P<title>[^<\[]+)(?:\[(?P<lang>[^\]]+)\])?</h1></a>.*?-->(?:.*?<br />)?\s*(?P<plot>[^<]+)'
|
||||||
patronNext = r'<link rel="next" href="([^"]+)"'
|
patronNext = r'<link rel="next" href="([^"]+)"'
|
||||||
action = 'check'
|
action = 'check'
|
||||||
return locals()
|
return locals()
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ def search(item, text):
|
|||||||
if item.contentType == 'tvshow': item.url = host + '/serietv/'
|
if item.contentType == 'tvshow': item.url = host + '/serietv/'
|
||||||
else: item.url = host
|
else: item.url = host
|
||||||
try:
|
try:
|
||||||
item.url = item.url + "?s=" + text.replace(' ', '+')
|
item.url = item.url + "/search/" + text.replace(' ', '+')
|
||||||
return peliculas(item)
|
return peliculas(item)
|
||||||
|
|
||||||
# Continua la ricerca in caso di errore
|
# Continua la ricerca in caso di errore
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def peliculas(item):
|
|||||||
patron = r'<a href="(?P<url>(?:https:\/\/.+?\/(?P<title>[^\/]+[a-zA-Z0-9\-]+)(?P<year>\d{4})))/".+?url\((?P<thumb>[^\)]+)\)">'
|
patron = r'<a href="(?P<url>(?:https:\/\/.+?\/(?P<title>[^\/]+[a-zA-Z0-9\-]+)(?P<year>\d{4})))/".+?url\((?P<thumb>[^\)]+)\)">'
|
||||||
elif item.contentType == 'tvshow':
|
elif item.contentType == 'tvshow':
|
||||||
if item.args == 'update':
|
if item.args == 'update':
|
||||||
patron = r'<a href="(?P<url>[^"]+)"[^<]+?url\((?P<thumb>.+?)\)">\s+<div class="titolo">(?P<title>.+?)(?: – Serie TV)?(?:\([sSuUbBiItTaA\-]+\))?[ ]?(?P<year>\d{4})?</div>[ ](?:<div class="genere">)?(?:[\w]+?\.?\s?[\s|S]?[\dx\-S]+?\s\(?(?P<lang>[iItTaA]+|[sSuUbBiItTaA\-]+)\)?\s?(?P<quality>[HD]+)?|.+?\(?(?P<lang2>[sSuUbBiItTaA\-]+)?\)?</div>)'
|
patron = r'<a href="(?P<url>[^"]+)"[^<]+?url\((?P<thumb>.+?)\)">\s*?<div class="titolo">(?P<title>.+?)(?: – Serie TV)?(?:\([sSuUbBiItTaA\-]+\))?[ ]?(?P<year>\d{4})?</div>\s*?(?:<div class="genere">)?(?:[\w]+?\.?\s?[\s|S]?[\dx\-S]+?\s\(?(?P<lang>[iItTaA]+|[sSuUbBiItTaA\-]+)\)?\s?(?P<quality>[HD]+)?|.+?\(?(?P<lang2>[sSuUbBiItTaA\-]+)?\)?</div>)'
|
||||||
pagination = 25
|
pagination = 25
|
||||||
else:
|
else:
|
||||||
patron = r'<a href="(?P<url>[^"]+)"\s*title="(?P<title>[^"\(]+)(?:"|\()(?:(?P<year>\d+)[^"]+)?.*?url\((?P<thumb>[^\)]+)\)(?:.*?<div class="voto">[^>]+>[^>]+>\s*(?P<rating>[^<]+))?.*?<div class="titolo">[^>]+>(?:<div class="genere">[^ ]*(?:\s\d+)?\s*(?:\()?(?P<lang>[^\)< ]+))?'
|
patron = r'<a href="(?P<url>[^"]+)"\s*title="(?P<title>[^"\(]+)(?:"|\()(?:(?P<year>\d+)[^"]+)?.*?url\((?P<thumb>[^\)]+)\)(?:.*?<div class="voto">[^>]+>[^>]+>\s*(?P<rating>[^<]+))?.*?<div class="titolo">[^>]+>(?:<div class="genere">[^ ]*(?:\s\d+)?\s*(?:\()?(?P<lang>[^\)< ]+))?'
|
||||||
@@ -77,6 +77,8 @@ def peliculas(item):
|
|||||||
item.title += support.typo(item.lang2, '_ [] color kod')
|
item.title += support.typo(item.lang2, '_ [] color kod')
|
||||||
if item.args == 'update':
|
if item.args == 'update':
|
||||||
item.title = item.title.replace('-', ' ')
|
item.title = item.title.replace('-', ' ')
|
||||||
|
if item.args == 'search':
|
||||||
|
item.contentType = 'tvshow' if 'serie-' in item.url else 'movie'
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"id": "discoveryplus",
|
||||||
|
"name": "Discovery + [free]",
|
||||||
|
"active": true,
|
||||||
|
"language": ["ita"],
|
||||||
|
"thumbnail": "discoveryplus.png",
|
||||||
|
"banner": "discoveryplus.png",
|
||||||
|
"categories": ["tvshow", "documentary", "live"],
|
||||||
|
"not_active": ["include_in_newest"],
|
||||||
|
"default_off": ["include_in_global_search"],
|
||||||
|
"settings": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Canale per Rai Play
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from core import support, jsontools
|
||||||
|
from platformcode import logger, config
|
||||||
|
typo = support.typo
|
||||||
|
session = requests.Session()
|
||||||
|
host = support.config.get_channel_url()
|
||||||
|
|
||||||
|
def getToken():
|
||||||
|
token = config.get_setting('token', 'discoveryplus', None)
|
||||||
|
if not token:
|
||||||
|
token = session.get('https://disco-api.discoveryplus.it/token?realm=dplayit').json()['data']['attributes']['token']
|
||||||
|
config.set_setting('token', token, 'discoveryplus')
|
||||||
|
return token
|
||||||
|
|
||||||
|
token = getToken()
|
||||||
|
|
||||||
|
api = "https://disco-api.discoveryplus.it"
|
||||||
|
headers = {'User-Agent': 'Mozilla/50.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0',
|
||||||
|
'Referer': host,
|
||||||
|
'Cookie' : 'st=' + token}
|
||||||
|
|
||||||
|
def Dict(item):
|
||||||
|
global pdict
|
||||||
|
pdict = session.get(api + '/cms/routes/{}?decorators=viewingHistory&include=default'.format(item.args), headers=headers).json()['included']
|
||||||
|
|
||||||
|
|
||||||
|
@support.menu
|
||||||
|
def mainlist(item):
|
||||||
|
top = [('Dirette {bold}', ['', 'live']),
|
||||||
|
('Programmi {bullet bold tv}', ['/programmi', 'peliculas']),
|
||||||
|
('Generi {bullet bold tv}', ['', 'genres'])]
|
||||||
|
|
||||||
|
search = ''
|
||||||
|
|
||||||
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
|
def liveDict():
|
||||||
|
livedict = {}
|
||||||
|
for key in session.get(api + '/cms/routes/home?decorators=viewingHistory&include=default', headers=headers).json()['included']:
|
||||||
|
if key['type'] == 'channel' and key.get('attributes',{}).get('hasLiveStream', '') and 'Free' in key.get('attributes',{}).get('packages', []):
|
||||||
|
title = key['attributes']['name']
|
||||||
|
livedict[title] = {}
|
||||||
|
livedict[title]['plot'] = key['attributes']['description']
|
||||||
|
livedict[title]['url'] = '{}/canali/{}'.format(host, key['attributes']['alternateId'])
|
||||||
|
livedict[title]['id'] = key['id']
|
||||||
|
return livedict
|
||||||
|
|
||||||
|
|
||||||
|
def search(item, text):
|
||||||
|
itemlist = []
|
||||||
|
item.args = 'search'
|
||||||
|
item.text = text
|
||||||
|
try:
|
||||||
|
itemlist = peliculas(item)
|
||||||
|
except:
|
||||||
|
import sys
|
||||||
|
for line in sys.exc_info():
|
||||||
|
support.logger.error("%s" % line)
|
||||||
|
|
||||||
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
|
def live(item):
|
||||||
|
logger.debug()
|
||||||
|
itemlist =[]
|
||||||
|
for name, values in liveDict().items():
|
||||||
|
itemlist.append(item.clone(title=typo(name), fulltitle=name, plot=values['plot'], url=values['url'], id=values['id'], action='play', forcethumb=True, no_return=True))
|
||||||
|
return support.thumb(itemlist, live=True)
|
||||||
|
|
||||||
|
|
||||||
|
def genres(item):
|
||||||
|
item.action = 'peliculas'
|
||||||
|
itemlist = [
|
||||||
|
item.clone(title='Attualità e inchiesta', url='/genere/attualita-e-inchiesta'),
|
||||||
|
item.clone(title='Beauty and style', url='/genere/beauty-and-style'),
|
||||||
|
item.clone(title='Serie TV', url='/genere/serie-tv'),
|
||||||
|
item.clone(title='Casa', url='/genere/casa'),
|
||||||
|
item.clone(title='Comedy', url='/genere/comedy'),
|
||||||
|
item.clone(title='Crime', url='/genere/crime'),
|
||||||
|
item.clone(title='Documentari', url='/genere/documentari'),
|
||||||
|
item.clone(title='Discovery + Originals', url='/genere/discoveryplus-original'),
|
||||||
|
item.clone(title='Food', url='/genere/food'),
|
||||||
|
item.clone(title='Medical', url='/genere/medical'),
|
||||||
|
item.clone(title='Motori', url='/genere/motori'),
|
||||||
|
item.clone(title='Natura', url='/genere/natura'),
|
||||||
|
item.clone(title='Paranormal', url='/genere/paranormal'),
|
||||||
|
item.clone(title='People', url='/genere/people'),
|
||||||
|
item.clone(title='Real Adventure', url='/genere/real-adventure'),
|
||||||
|
item.clone(title='Real Life', url='/genere/real-life'),
|
||||||
|
item.clone(title='Scienza e Spazio', url='/genere/scienza-e-spazio'),
|
||||||
|
item.clone(title='Sex and love', url='/genere/sex-and-love'),
|
||||||
|
item.clone(title='Sport', url='/genere/sport'),
|
||||||
|
item.clone(title='Talent Show', url='/genere/talent-show'),
|
||||||
|
]
|
||||||
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
|
def peliculas(item):
|
||||||
|
logger.debug()
|
||||||
|
itemlist =[]
|
||||||
|
if 'search' in item.args:
|
||||||
|
pdict = session.get(api + '/content/shows?include=genres,images,primaryChannel.images,contentPackages&page[size]=12&query=' + item.text, headers=headers).json()['data']
|
||||||
|
else:
|
||||||
|
pdict = session.get(api + '/cms/routes{}?decorators=viewingHistory&include=default'.format(item.url), headers=headers).json()['included']
|
||||||
|
images = list(filter(lambda x: x['type'] == 'image', pdict))
|
||||||
|
|
||||||
|
for key in pdict:
|
||||||
|
if key['type'] == 'show' and 'Free' in str(key.get('relationships',{}).get('contentPackages',{}).get('data',[])):
|
||||||
|
title = key['attributes']['name']
|
||||||
|
plot = key['attributes'].get('description','')
|
||||||
|
url = '{}/programmi/{}'.format(host, key['attributes']['alternateId'])
|
||||||
|
seasons = key['attributes']['seasonNumbers']
|
||||||
|
thumbs = [image['attributes']['src'] for image in images if image['id'] == key['relationships']['images']['data'][0]['id']]
|
||||||
|
thumb = thumbs[0] if thumbs else item.thumbnail
|
||||||
|
fanarts = [image['attributes']['src'] for image in images if len(key['relationships']['images']['data']) > 1 and image['id'] == key['relationships']['images']['data'][1]['id']]
|
||||||
|
fanart = fanarts[0] if fanarts else item.fanart
|
||||||
|
itemlist.append(
|
||||||
|
item.clone(title=typo(title,'bold'),
|
||||||
|
fulltitle=title,
|
||||||
|
plot=plot,
|
||||||
|
url=url,
|
||||||
|
programid=key['attributes']['alternateId'],
|
||||||
|
id=key['id'],
|
||||||
|
seasons=seasons,
|
||||||
|
action='episodios',
|
||||||
|
thumbnail=thumb,
|
||||||
|
fanart=fanart,
|
||||||
|
contentType='tvshow'))
|
||||||
|
|
||||||
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
|
def episodios(item):
|
||||||
|
logger.debug()
|
||||||
|
itemlist =[]
|
||||||
|
pdict = session.get(api + '/cms/routes/programmi/{}?decorators=viewingHistory&include=default'.format(item.programid), headers=headers).json()['included']
|
||||||
|
|
||||||
|
for key in pdict:
|
||||||
|
if key['type'] == 'collection' and key.get('attributes',{}).get('component',{}).get('id', '') == 'tabbed-content':
|
||||||
|
mandatory = key['attributes']['component']['mandatoryParams']
|
||||||
|
for option in key['attributes']['component']['filters'][0]['options']:
|
||||||
|
url = '{}/cms/collections/{}?decorators=viewingHistory&include=default&{}&{}'.format(api, key['id'], mandatory, option['parameter'])
|
||||||
|
season = session.get(url, headers=headers).json()
|
||||||
|
if season.get('included', {}):
|
||||||
|
for episode in season['included']:
|
||||||
|
if episode['type'] == 'video' and 'Free' in episode['attributes']['packages']:
|
||||||
|
title = '{}x{:02d} - {}'.format(option['id'], episode['attributes']['episodeNumber'], episode['attributes']['name'])
|
||||||
|
plot = episode['attributes']['description']
|
||||||
|
itemlist.append(
|
||||||
|
item.clone(title=typo(title,'bold'),
|
||||||
|
fulltitle=title,
|
||||||
|
plot=plot,
|
||||||
|
id=episode['id'],
|
||||||
|
action='play',
|
||||||
|
contentType='episode',
|
||||||
|
season=option['id'],
|
||||||
|
episode=episode['attributes']['episodeNumber'],
|
||||||
|
forcethumb=True,
|
||||||
|
no_return=True))
|
||||||
|
|
||||||
|
if itemlist: itemlist.sort(key=lambda it: (int(it.season), int(it.episode)))
|
||||||
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
|
def play(item):
|
||||||
|
if item.livefilter:
|
||||||
|
item.id = liveDict()[item.livefilter]['id']
|
||||||
|
item.fulltitle = item.livefilter
|
||||||
|
item.forcethumb = True
|
||||||
|
item.no_return = True
|
||||||
|
support.thumb(item, live=True)
|
||||||
|
if item.contentType == 'episode': data = session.get('{}/playback/v2/videoPlaybackInfo/{}?usePreAuth=true'.format(api, item.id), headers=headers).json().get('data',{}).get('attributes',{})
|
||||||
|
else: data = session.get('{}/playback/v2/channelPlaybackInfo/{}?usePreAuth=true'.format(api, item.id), headers=headers).json().get('data',{}).get('attributes',{})
|
||||||
|
if data.get('protection', {}).get('drm_enabled',True):
|
||||||
|
url = data['streaming']['dash']['url']
|
||||||
|
item.drm = 'com.widevine.alpha'
|
||||||
|
item.license = data['protection']['schemes']['widevine']['licenseUrl'] + '|PreAuthorization=' + data['protection']['drmToken'] + '|R{SSM}|'
|
||||||
|
else:
|
||||||
|
url = data['streaming']['hls']['url']
|
||||||
|
return support.servertools.find_video_items(item, data=url)
|
||||||
@@ -137,7 +137,7 @@ def findvideos(item):
|
|||||||
if 'sub' in lang.lower():
|
if 'sub' in lang.lower():
|
||||||
language = 'Sub-' + language
|
language = 'Sub-' + language
|
||||||
quality = url.split('/')[-1].split('?')[0]
|
quality = url.split('/')[-1].split('?')[0]
|
||||||
url += "|User-Agent=" + support.httptools.get_user_agent() + '&Referer=' + item.url
|
url += '|User-Agent=' + support.httptools.get_user_agent() + '&Referer=' + url
|
||||||
|
|
||||||
itemlist.append(item.clone(action="play", title=language, url=url, contentLanguage = language, quality = quality, order = quality.replace('p','').zfill(4), server='directo',))
|
itemlist.append(item.clone(action="play", title=language, url=url, contentLanguage = language, quality = quality, order = quality.replace('p','').zfill(4), server='directo',))
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ def mainlist(item):
|
|||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
|
# debug = True
|
||||||
action = 'episodios'
|
action = 'episodios'
|
||||||
if item.args == 'newest':
|
if item.args == 'newest':
|
||||||
item.contentType = 'episode'
|
item.contentType = 'episode'
|
||||||
patron = r'<span class="serieTitle" style="font-size:20px">(?P<title>[^<]+) –\s*<a href="(?P<url>[^"]+)"[^>]*>\s?(?P<episode>\d+[×x]\d+-\d+|\d+[×x]\d+) (?P<title2>[^<\(]+)\s?\(?(?P<lang>SUB ITA)?\)?</a>'
|
patron = r'<span class="serieTitle" style="font-size:20px">(?P<title>[^<]+) –\s*<a href="(?P<url>[^"]+)"[^>]*>\s?(?P<episode>\d+[×x]\d+-\d+|\d+[×x]\d+) (?P<title2>[^<\(]+)\s?\(?(?P<lang>SUB ITA)?\)?</a>'
|
||||||
pagination = ''
|
pagination = ''
|
||||||
else:
|
else:
|
||||||
patron = r'<div class="post-thumb">.*?\s<img src="(?P<thumb>[^"]+)".*?><a href="(?P<url>[^"]+)"[^>]+>(?P<title>.+?)\s?(?: Serie Tv)?\s?\(?(?P<year>\d{4})?\)?<\/a><\/h2>'
|
patron = r'<div class="post-thumb">.*?<img src="(?P<thumb>[^"]+)".*?><a href="(?P<url>[^"]+)"[^>]+>(?P<title>.+?)\s?(?: Serie Tv)?\s?\(?(?P<year>\d{4})?\)?<\/a><\/h2>'
|
||||||
patronNext=r'a class="next page-numbers" href="?([^>"]+)">Avanti »</a>'
|
patronNext=r'a class="next page-numbers" href="?([^>"]+)">Avanti »</a>'
|
||||||
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from platformcode import config
|
|||||||
|
|
||||||
def findhost(url):
|
def findhost(url):
|
||||||
page = httptools.downloadpage(url).data
|
page = httptools.downloadpage(url).data
|
||||||
url = support.scrapertools.find_single_match(page, 'Il nuovo indirizzo di FILMPERTUTTI è <a href="([^"]+)')
|
url = support.scrapertools.find_single_match(page, 'Il nuovo indirizzo di FILMPERTUTTI è ?<a href="([^"]+)')
|
||||||
return url
|
return url
|
||||||
|
|
||||||
host = config.get_channel_url(findhost)
|
host = config.get_channel_url(findhost)
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ def peliculas(item):
|
|||||||
if item.args == 'alternative':
|
if item.args == 'alternative':
|
||||||
patron = r'<a title="(?P<title>[^\(]+)\(\s*(?P<year>\d+)\)\s\D+(?P<quality>\d+p) ... (?P<lang>[^ ]+).*?[^"]+"\s*href="(?P<url>[^"]+)'
|
patron = r'<a title="(?P<title>[^\(]+)\(\s*(?P<year>\d+)\)\s\D+(?P<quality>\d+p) ... (?P<lang>[^ ]+).*?[^"]+"\s*href="(?P<url>[^"]+)'
|
||||||
else:
|
else:
|
||||||
patron = r'<a href="(?P<url>[^"]+)" rel="?[0-9]+"? title="(?P<title>[^\(]+)(?!\()\s*\((?P<year>\d+)\)\s\D+(?P<quality>\d+p).{3}(?P<lang>[^ ]+).*?<img id="?cov"?.*?src="(?P<thumb>[^"]+)'
|
patron = r'<a href="(?P<url>[^"]+)" (?:rel="?[0-9]+"?)? title="(?P<title>[^\(]+)(?!\()\s*\((?P<year>\d+)\)\s(?:[^\]]+\])?\D+(?P<quality>\d+p).{3}(?P<lang>[^ ]+).*?<img id="?cov"?.*?src="(?P<thumb>[^"]+)'
|
||||||
patronNext = r'rel="?next"? href="([^"]+)"'
|
patronNext = r'rel="?next"? href="([^"]+)"'
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ def mainlist(item):
|
|||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
info()
|
info()
|
||||||
# debugBlock = True
|
# debugBlock = True
|
||||||
|
# debug=True
|
||||||
|
|
||||||
if item.args == 'search':
|
if item.args == 'search':
|
||||||
patronBlock = r'<div class="search-page">(?P<block>.*?)<footer class="main">'
|
patronBlock = r'<div class="search-page">(?P<block>.*?)<footer class="main">'
|
||||||
@@ -79,13 +80,13 @@ def peliculas(item):
|
|||||||
action = 'episodios'
|
action = 'episodios'
|
||||||
if item.args == 'update':
|
if item.args == 'update':
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
patron = r'<div class="poster"><img src="(?P<thumb>[^"]+)"[^>]+>[^>]+><a href="(?P<url>[^"]+)">[^>]+>(?P<episode>[\d\-x]+)[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>.+?)(?:\[(?P<lang>Sub-ITA|Sub-ita)\])?<[^>]+>[^>]+>[^>]+>[^>]+>(?P<quality>[HD]+)?(?:.+?)?/span><p class="serie"'
|
patron = r'<div class="poster"><img src="(?P<thumb>[^"]+)"(?:[^>]+>){2}<a href="(?P<url>[^"]+)">[^>]+>(?P<episode>[\d\-x]+)(?:[^>]+>){4}(?P<title>.+?)(?:\[(?P<lang>[SsuUbBiItTaA-]{7})\])?<(?:[^>]+>){4}(?P<quality>[HDWEBRIP-]+)?(?:.+?)?/span><p class="serie"'
|
||||||
pagination = 25
|
pagination = 25
|
||||||
def itemHook(item):
|
def itemHook(item):
|
||||||
item.contentType = 'episode'
|
item.contentType = 'episode'
|
||||||
return item
|
return item
|
||||||
else:
|
else:
|
||||||
patron = r'<div class="poster">\s?<a href="(?P<url>[^"]+)"><img src="(?P<thumb>[^"]+)" alt="[^"]+"><\/a>[^>]+>[^>]+>[^>]+> (?P<rating>[0-9.]+)<[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>.+?)[ ]?(?:\[(?P<lang>Sub-ITA|Sub-ita)\])?<[^>]+>[^>]+>[^>]+>(?P<year>[0-9]{4})?[^<]*(?:<.*?<div class="texto">(?P<plot>[^<]+))?'
|
patron = r'<div class="poster">\s?<a href="(?P<url>[^"]+)"><img src="(?P<thumb>[^"]+)" alt="[^"]+"><\/a>[^>]+>[^>]+>[^>]+> (?P<rating>[0-9.]+)<[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>.+?)[ ]?(?:\[(?P<lang>Sub-ITA|Sub-ita)\])?<[^>]+>[^>]+>[^>]+>(?P<year>[0-9]{4})?[^<]*(?:<.*?<div class="texto">(?P<plot>[^<]+)?)?'
|
||||||
patronNext = '<span class="current">[^<]+<[^>]+><a href=[\'"]([^\'"]+)[\'"]'
|
patronNext = '<span class="current">[^<]+<[^>]+><a href=[\'"]([^\'"]+)[\'"]'
|
||||||
|
|
||||||
#support.regexDbg(item, patron, headers)
|
#support.regexDbg(item, patron, headers)
|
||||||
@@ -128,13 +129,13 @@ def search(item, text):
|
|||||||
itemlist = []
|
itemlist = []
|
||||||
text = text.replace(' ', '+')
|
text = text.replace(' ', '+')
|
||||||
item.url = host + "?s=" + text
|
item.url = host + "?s=" + text
|
||||||
# try:
|
try:
|
||||||
item.args = 'search'
|
item.args = 'search'
|
||||||
return peliculas(item)
|
return peliculas(item)
|
||||||
# except:
|
except:
|
||||||
# import sys
|
import sys
|
||||||
# for line in sys.exc_info():
|
for line in sys.exc_info():
|
||||||
# info("%s" % line)
|
info("%s" % line)
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
@@ -25,16 +25,18 @@ def mainlist(item):
|
|||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
|
# debug=True
|
||||||
blacklist = ['Aggiornamento Episodi']
|
blacklist = ['Aggiornamento Episodi']
|
||||||
action = 'episodios'
|
action = 'episodios'
|
||||||
patron = r'<div class="post-thumb">\s*<a href="(?P<url>[^"]+)" title="(?P<title>[^"\[]+)[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^>]+>'
|
patron = r'<div class="post-thumb">\s*<a href="(?P<url>[^"]+)" title="(?P<title>[^"\[]+)[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^>]+>'
|
||||||
|
|
||||||
if item.args == 'update':
|
if item.args == 'update':
|
||||||
pagination = ''
|
pagination = ''
|
||||||
patron = r'br />(?:[^>]+>)?(?P<title>[^–]+)[^<]+<a href="(?P<url>[^"]+)">(?P<episode>[^ ]+)\s*(?P<title2>[^\(<]+)(?:\((?P<lang>[^\)]+))?'
|
#patron = r'br />(?:[^>]+>)?(?P<title>[^–]+)[^<]+<a href="(?P<url>[^"]+)">(?P<episode>[^ ]+)\s*(?P<title2>[^\(<]+)(?:\((?P<lang>[^\)]+))?'
|
||||||
|
patron = r'br[\s/]*>(?:\s*<[^>]+>)*(?P<title>[^–<]+)[^<]+<a href="(?P<url>[^"]+)"[^>]*>(?:[^,]{0,80}[, ]{2})*(?P<episode>[\S]+)\s*(?P<title2>[^\(<]+)(?:\((?P<lang>[^\)]+))?'
|
||||||
action = 'episodios'
|
action = 'episodios'
|
||||||
if item.args == 'top':
|
if item.args == 'top':
|
||||||
patron = r'<a href="(?P<url>[^"]+)">(?P<title>[^<]+)</a>[^>]+>[^>]+>[^>]+><img.*?src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>:\s*(?P<rating>[^/]+)'
|
patron = r'<a href="(?P<url>[^"]+)">(?P<title>[^<]+)</a>(?:[^>]+>){3}<img.*?src="(?P<thumb>[^"]+)"[^>]+>(?:[^>]+>){5}:\s*(?P<rating>[^/]+)'
|
||||||
if item.args =='a-z':
|
if item.args =='a-z':
|
||||||
pagination = ''
|
pagination = ''
|
||||||
patron = r'<li ><a href="(?P<url>[^"]+)" title="(?P<title>[^"]+)"'
|
patron = r'<li ><a href="(?P<url>[^"]+)" title="(?P<title>[^"]+)"'
|
||||||
|
|||||||
+13
-1
@@ -71,7 +71,14 @@ def replay(item):
|
|||||||
def search(item, text):
|
def search(item, text):
|
||||||
item.url = host + '/tutti-i-programmi'
|
item.url = host + '/tutti-i-programmi'
|
||||||
item.search = text
|
item.search = text
|
||||||
return peliculas(item)
|
try:
|
||||||
|
return peliculas(item)
|
||||||
|
except:
|
||||||
|
import sys
|
||||||
|
for line in sys.exc_info():
|
||||||
|
support.info('search log:', line)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
@@ -118,6 +125,11 @@ def episodios(item):
|
|||||||
|
|
||||||
def play(item):
|
def play(item):
|
||||||
support.info()
|
support.info()
|
||||||
|
if item.livefilter:
|
||||||
|
for it in live(item):
|
||||||
|
if it.fulltitle == item.livefilter:
|
||||||
|
item = it
|
||||||
|
break
|
||||||
data = support.match(item).data
|
data = support.match(item).data
|
||||||
match = support.match(data, patron='/content/entry/data/(.*?).mp4').match
|
match = support.match(data, patron='/content/entry/data/(.*?).mp4').match
|
||||||
if match:
|
if match:
|
||||||
|
|||||||
+40
-19
@@ -10,6 +10,7 @@ if sys.version_info[0] >= 3: from urllib.parse import urlencode, quote
|
|||||||
else: from urllib import urlencode, quote
|
else: from urllib import urlencode, quote
|
||||||
if sys.version_info[0] >= 3: from concurrent import futures
|
if sys.version_info[0] >= 3: from concurrent import futures
|
||||||
else: from concurrent_py2 import futures
|
else: from concurrent_py2 import futures
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
host = ''
|
host = ''
|
||||||
DRM = 'com.widevine.alpha'
|
DRM = 'com.widevine.alpha'
|
||||||
@@ -120,10 +121,9 @@ def menu(item):
|
|||||||
return itemlist
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
def live(item):
|
def liveDict():
|
||||||
support.info()
|
livedict = OrderedDict({})
|
||||||
itemlist = []
|
json = current_session.get('https://feed.entertainment.tv.theplatform.eu/f/PR1GhC/mediaset-prod-all-stations?sort=ShortTitle').json()['entries']
|
||||||
json = current_session.get(item.url).json()['entries']
|
|
||||||
for it in json:
|
for it in json:
|
||||||
urls = []
|
urls = []
|
||||||
if it['tuningInstruction'] and not it['mediasetstation$digitalOnly']:
|
if it['tuningInstruction'] and not it['mediasetstation$digitalOnly']:
|
||||||
@@ -133,18 +133,25 @@ def live(item):
|
|||||||
else:
|
else:
|
||||||
for key in it['tuningInstruction']['urn:theplatform:tv:location:any']:
|
for key in it['tuningInstruction']['urn:theplatform:tv:location:any']:
|
||||||
urls += key['publicUrls']
|
urls += key['publicUrls']
|
||||||
plot = support.typo(guide['currentListing']['mediasetlisting$epgTitle'],'bold') + '\n' + guide['currentListing']['mediasetlisting$shortDescription'] + '\n' + guide['currentListing']['description'] + '\n\n' + support.typo('A Seguire:' + guide['nextListing']['mediasetlisting$epgTitle'], 'bold')
|
title = it['title']
|
||||||
|
livedict[title] = {}
|
||||||
|
livedict[title]['urls'] = urls
|
||||||
|
livedict[title]['plot'] = support.typo(guide['currentListing']['mediasetlisting$epgTitle'],'bold') + '\n' + guide['currentListing']['mediasetlisting$shortDescription'] + '\n' + guide['currentListing']['description'] + '\n\n' + support.typo('A Seguire:' + guide['nextListing']['mediasetlisting$epgTitle'], 'bold')
|
||||||
|
return livedict
|
||||||
|
|
||||||
itemlist.append(item.clone(title=support.typo(it['title'], 'bold'),
|
def live(item):
|
||||||
fulltitle=it['title'],
|
support.info()
|
||||||
show=it['title'],
|
itemlist = []
|
||||||
contentTitle=it['title'],
|
for key, value in liveDict().items():
|
||||||
thumbnail=it['thumbnails']['channel_logo-100x100']['url'],
|
itemlist.append(item.clone(title=support.typo(key, 'bold'),
|
||||||
forcethumb=True,
|
fulltitle=key,
|
||||||
urls=urls,
|
show=key,
|
||||||
plot=plot,
|
contentTitle=key,
|
||||||
action='play',
|
forcethumb=True,
|
||||||
no_return=True))
|
urls=value['urls'],
|
||||||
|
plot=value['plot'],
|
||||||
|
action='play',
|
||||||
|
no_return=True))
|
||||||
return support.thumb(itemlist, live=True)
|
return support.thumb(itemlist, live=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -204,7 +211,9 @@ def peliculas(item):
|
|||||||
plot=it['longDescription'] if 'longDescription' in it else it['description'] if 'description' in it else '',
|
plot=it['longDescription'] if 'longDescription' in it else it['description'] if 'description' in it else '',
|
||||||
urls=urls,
|
urls=urls,
|
||||||
seriesid = it.get('seriesId',''),
|
seriesid = it.get('seriesId',''),
|
||||||
url=it['mediasetprogram$pageUrl']))
|
url=it['mediasetprogram$pageUrl'],
|
||||||
|
forcethumb=True,
|
||||||
|
no_return=True))
|
||||||
return itemlist
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
@@ -246,7 +255,7 @@ def episodios(item):
|
|||||||
urls.append(key['publicUrl'])
|
urls.append(key['publicUrl'])
|
||||||
if urls:
|
if urls:
|
||||||
title = it['title'].split('-')[-1].strip()
|
title = it['title'].split('-')[-1].strip()
|
||||||
if it['tvSeasonNumber'] and it['tvSeasonEpisodeNumber']:
|
if it['tvSeasonNumber'] and it['tvSeasonEpisodeNumber'] and 'puntata del' not in title.lower():
|
||||||
item.infoLabels['season'] = it['tvSeasonNumber']
|
item.infoLabels['season'] = it['tvSeasonNumber']
|
||||||
item.infoLabels['episode'] = it['tvSeasonEpisodeNumber']
|
item.infoLabels['episode'] = it['tvSeasonEpisodeNumber']
|
||||||
episode = '%dx%02d - ' % (it['tvSeasonNumber'], it['tvSeasonEpisodeNumber'])
|
episode = '%dx%02d - ' % (it['tvSeasonNumber'], it['tvSeasonEpisodeNumber'])
|
||||||
@@ -258,7 +267,10 @@ def episodios(item):
|
|||||||
fanart=it['thumbnails']['image_keyframe_poster-1280x720']['url'] if 'image_keyframe_poster-1280x720' in it['thumbnails'] else '',
|
fanart=it['thumbnails']['image_keyframe_poster-1280x720']['url'] if 'image_keyframe_poster-1280x720' in it['thumbnails'] else '',
|
||||||
plot=it['longDescription'] if 'longDescription' in it else it['description'],
|
plot=it['longDescription'] if 'longDescription' in it else it['description'],
|
||||||
urls=urls,
|
urls=urls,
|
||||||
url=it['mediasetprogram$pageUrl']))
|
url=it['mediasetprogram$pageUrl'],
|
||||||
|
year=it.get('year',''),
|
||||||
|
forcethumb=True,
|
||||||
|
no_return=True))
|
||||||
if episode:
|
if episode:
|
||||||
itemlist = sorted(itemlist, key=lambda it: it.title)
|
itemlist = sorted(itemlist, key=lambda it: it.title)
|
||||||
support.videolibrary(itemlist, item)
|
support.videolibrary(itemlist, item)
|
||||||
@@ -267,12 +279,17 @@ def episodios(item):
|
|||||||
|
|
||||||
def findvideos(item):
|
def findvideos(item):
|
||||||
support.info()
|
support.info()
|
||||||
itemlist = [support.Item(server='directo', title='Direct', url=item.urls, action='play')]
|
itemlist = [support.Item(server='directo', title='Mediaset Play', url=item.urls, action='play')]
|
||||||
return support.server(item, itemlist=itemlist, Download=False)
|
return support.server(item, itemlist=itemlist, Download=False)
|
||||||
|
|
||||||
|
|
||||||
def play(item):
|
def play(item):
|
||||||
support.info()
|
support.info()
|
||||||
|
if item.livefilter:
|
||||||
|
d = liveDict()[item.livefilter]
|
||||||
|
# support.dbg()
|
||||||
|
item = item.clone(title=support.typo(item.livefilter, 'bold'), fulltitle=item.livefilter, urls=d['urls'], plot=d['plot'], action='play', forcethumb=True, no_return=True)
|
||||||
|
support.thumb(item, live=True)
|
||||||
if not item.urls: urls = item.url
|
if not item.urls: urls = item.url
|
||||||
else: urls = item.urls
|
else: urls = item.urls
|
||||||
data = ''
|
data = ''
|
||||||
@@ -285,6 +302,10 @@ def play(item):
|
|||||||
item.drm = DRM
|
item.drm = DRM
|
||||||
item.license = lic_url % support.match(sec_data, patron=r'pid=([^|]+)').match
|
item.license = lic_url % support.match(sec_data, patron=r'pid=([^|]+)').match
|
||||||
data = support.match(sec_data, patron=r'<video src="([^"]+)').match
|
data = support.match(sec_data, patron=r'<video src="([^"]+)').match
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
support.dbg()
|
||||||
|
data = url
|
||||||
|
|
||||||
return support.servertools.find_video_items(item, data=data)
|
return support.servertools.find_video_items(item, data=data)
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,10 @@ def mainlist(item):
|
|||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
|
# debug=True
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
patron= r'<img src="[^"]+" alt="(?P<title>[^"]+)" data-echo="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+><a href="(?P<url>[^"]+)"'
|
patron= r'<img src="[^"]+" alt="(?P<title>[^"]+)" data-echo="(?P<thumb>[^"]+)"(?:[^>]+>){7}<a href="(?P<url>[^"]+)"'
|
||||||
patronNext = r'<a href="([^"]+)">»'
|
patronNext = r'<a href="([^"]+)">(?:»|»)'
|
||||||
typeContentDict = {'': 'music'}
|
typeContentDict = {'': 'music'}
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ def peliculas(item):
|
|||||||
# debug=True
|
# debug=True
|
||||||
if item.args == 'last':
|
if item.args == 'last':
|
||||||
patronBlock = r'<table>(?P<block>.*?)</table>'
|
patronBlock = r'<table>(?P<block>.*?)</table>'
|
||||||
patron = r'<tr><td><a href="(?P<url>[^"]+)">\s*[^>]+>(?P<title>.*?)(?:\s(?P<year>\d{4}))? (?:Streaming|</b>)'
|
patron = r'<tr><td><a href="(?P<url>[^"]+)">\s*[^>]+>(?P<title>.*?)(?:\s(?P<year>\d{4}))?\s*(?:Streaming|</b>)'
|
||||||
elif item.args == 'lastep':
|
elif item.args == 'lastep':
|
||||||
patronBlock = r'<table>(?P<block>.*?)</table>'
|
patronBlock = r'<table>(?P<block>.*?)</table>'
|
||||||
patron = r'<td>\s*<a href="[^>]+>(?P<title>.*?)(?:\s(?P<year>\d{4}))?\s(?:(?P<episode>(?:\d+x\d+|\d+)))\s*(?P<title2>[^<]+)(?P<url>.*?)<tr>'
|
patron = r'<td>\s*<a href="[^>]+>(?P<title>.*?)(?:\s(?P<year>\d{4}))?\s(?:(?P<episode>(?:\d+x\d+|\d+)))\s*(?P<title2>[^<]+)(?P<url>.*?)<tr>'
|
||||||
|
|||||||
+23
-10
@@ -5,6 +5,7 @@
|
|||||||
import inspect
|
import inspect
|
||||||
from core import support, jsontools
|
from core import support, jsontools
|
||||||
from platformcode import autorenumber, logger
|
from platformcode import autorenumber, logger
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
host = support.config.get_channel_url()
|
host = support.config.get_channel_url()
|
||||||
headers = [['Referer', host]]
|
headers = [['Referer', host]]
|
||||||
@@ -39,24 +40,31 @@ def search(item, text):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def live(item):
|
def liveDict():
|
||||||
logger.debug()
|
livedict = OrderedDict({})
|
||||||
itemlist=[]
|
|
||||||
urls=[]
|
urls=[]
|
||||||
matches = support.match(host, patron=r'(/diretta-tv/[^"]+)"[^>]+>([^ ]+)').matches
|
matches = support.match(host, patron=r'(/diretta-tv/[^"]+)"[^>]+>([^ ]+)').matches
|
||||||
from datetime import date
|
from datetime import date
|
||||||
today = date.today()
|
today = date.today()
|
||||||
channels = jsontools.load(support.match(host + '/api/more/tvschedule/' + str(today.year) + str(today.month) + str(today.day)).data)['channels']
|
channels = jsontools.load(support.match(host + '/api/more/tvschedule/' + str(today.year) + str(today.month) + str(today.day)).data)['channels']
|
||||||
ch_dict = {}
|
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
ch_dict[channel['label']] = channel['channelId']
|
title = channel['label']
|
||||||
|
livedict[title] = {}
|
||||||
|
livedict[title]['id'] = channel['channelId']
|
||||||
for url, title in matches:
|
for url, title in matches:
|
||||||
if url not in urls:
|
if url not in urls:
|
||||||
urls.append(url)
|
urls.append(url)
|
||||||
info = jsontools.load(support.match(host +'/api/on-air?channelId=' + ch_dict[title]).data)
|
livedict[title]['url'] = host + url
|
||||||
support.info(info)
|
info = jsontools.load(support.match(host +'/api/on-air?channelId=' + livedict[title]['id']).data)
|
||||||
plot= '[B]' + info['seriesTitle'] +'[/B]\n' + info['description'] if 'seriesTitle' in info else ''
|
livedict[title]['plot']= '[B]' + info['seriesTitle'] +'[/B]\n' + info['description'] if 'seriesTitle' in info else ''
|
||||||
itemlist.append(item.clone(title=support.typo(title,'bold'), contentTitle=title, fulltitle=title, show=title, url=host+url, plot=plot, action='play', forcethumb=True, no_return=True))
|
return livedict
|
||||||
|
|
||||||
|
def live(item):
|
||||||
|
logger.debug()
|
||||||
|
itemlist=[]
|
||||||
|
for key, value in liveDict().items():
|
||||||
|
itemlist.append(item.clone(title=support.typo(key,'bold'), contentTitle=key, fulltitle=key, show=key, url=value['url'], plot=value['plot'], action='play', forcethumb=True, no_return=True))
|
||||||
return support.thumb(itemlist, live=True)
|
return support.thumb(itemlist, live=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -154,4 +162,9 @@ def findvideos(item):
|
|||||||
|
|
||||||
def play(item):
|
def play(item):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
return support.servertools.find_video_items(item, data=item.url)
|
item.server = 'paramount_server'
|
||||||
|
if item.livefilter:
|
||||||
|
d = liveDict()[item.livefilter]
|
||||||
|
item = item.clone(title=support.typo(item.livefilter, 'bold'), fulltitle=item.livefilter, url=d['url'], plot=d['plot'], action='play', forcethumb=True, no_return=True)
|
||||||
|
support.thumb(item, live=True)
|
||||||
|
return [item]
|
||||||
+50
-26
@@ -4,12 +4,14 @@
|
|||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
import requests, sys, inspect
|
import requests, sys, inspect
|
||||||
from core import support
|
from core import support, channeltools
|
||||||
from platformcode import autorenumber, logger
|
from platformcode import autorenumber, logger, platformtools
|
||||||
|
from collections import OrderedDict
|
||||||
if sys.version_info[0] >= 3:
|
if sys.version_info[0] >= 3:
|
||||||
from concurrent import futures
|
from concurrent import futures
|
||||||
else:
|
else:
|
||||||
from concurrent_py2 import futures
|
from concurrent_py2 import futures
|
||||||
|
|
||||||
current_session = requests.Session()
|
current_session = requests.Session()
|
||||||
host = support.config.get_channel_url()
|
host = support.config.get_channel_url()
|
||||||
onair = host + '/palinsesto/onAir.json'
|
onair = host + '/palinsesto/onAir.json'
|
||||||
@@ -17,19 +19,19 @@ onair = host + '/palinsesto/onAir.json'
|
|||||||
|
|
||||||
@support.menu
|
@support.menu
|
||||||
def mainlist(item):
|
def mainlist(item):
|
||||||
top = [('Dirette {bold}', ['/dl/RaiPlay/2016/PublishingBlock-9a2ff311-fcf0-4539-8f8f-c4fee2a71d58.html?json', 'live']),
|
top = [('Dirette {bold}', ['', 'live']),
|
||||||
('Replay {bold}', ['/dl/RaiPlay/2016/PublishingBlock-9a2ff311-fcf0-4539-8f8f-c4fee2a71d58.html?json', 'replay_menu'])]
|
('Replay {bold}', ['/dl/RaiPlay/2016/PublishingBlock-9a2ff311-fcf0-4539-8f8f-c4fee2a71d58.html?json', 'replay_menu'])]
|
||||||
|
|
||||||
menu = [('Film {bullet bold}', ['/film/index.json', 'menu']),
|
menu = [('Film {bullet bold}', ['/tipologia/film/index.json', 'menu']),
|
||||||
('Serie TV {bullet bold}', ['/serietv/index.json', 'menu']),
|
('Serie TV {bullet bold}', ['/tipologia/serietv/index.json', 'menu']),
|
||||||
('Fiction {bullet bold}', ['/fiction/index.json', 'menu']),
|
('Fiction {bullet bold}', ['/tipologia/fiction/index.json', 'menu']),
|
||||||
('Documentari {bullet bold}', ['/documentari/index.json', 'menu']),
|
('Documentari {bullet bold}', ['/tipologia/documentari/index.json', 'menu']),
|
||||||
('Programmi TV{bullet bold}', ['/programmi/index.json', 'menu']),
|
('Programmi TV{bullet bold}', ['/tipologia/programmi/index.json', 'menu']),
|
||||||
('Programmi per Bambini {bullet bold}', ['/bambini/index.json', 'menu']),
|
('Programmi per Bambini {bullet bold}', ['/tipologia/bambini/index.json', 'menu']),
|
||||||
('Teen {bullet bold}', ['/teen/index.json', 'learning']),
|
('Teen {bullet bold}', ['/tipologia/teen/index.json', 'learning']),
|
||||||
('Learning {bullet bold}', ['/learning/index.json', 'learning']),
|
('Learning {bullet bold}', ['/tipologia/learning/index.json', 'learning']),
|
||||||
('Teche Rai {bullet bold storia}', ['/techerai/index.json', 'menu']),
|
('Teche Rai {bullet bold storia}', ['/tipologia/techerai/index.json', 'menu']),
|
||||||
('Musica e Teatro {bullet bold}', ['/performing-arts/index.json', 'menu'])
|
('Musica e Teatro {bullet bold}', ['/tipologia/musica-e-teatro/index.json', 'menu'])
|
||||||
]
|
]
|
||||||
|
|
||||||
search = ''
|
search = ''
|
||||||
@@ -157,24 +159,33 @@ def Type(item):
|
|||||||
return select(item)
|
return select(item)
|
||||||
|
|
||||||
|
|
||||||
def live(item):
|
def liveDict():
|
||||||
support.info()
|
livedict = OrderedDict({})
|
||||||
itemlist =[]
|
info = {}
|
||||||
info={}
|
url = host + '/dirette.json'
|
||||||
json = current_session.get(item.url).json()['dirette']
|
json = current_session.get(url).json()['contents']
|
||||||
onAir = current_session.get(onair).json()['on_air']
|
onAir = current_session.get(onair).json()['on_air']
|
||||||
support.info(onAir)
|
|
||||||
for key in onAir:
|
for key in onAir:
|
||||||
channel = key['channel']
|
channel = key['channel']
|
||||||
info[channel] = {}
|
info[channel] = {}
|
||||||
info[channel]['fanart'] = getUrl(key['currentItem']['image'])
|
info[channel]['fanart'] = getUrl(key['currentItem']['image'])
|
||||||
info[channel]['plot'] = support.typo(key['currentItem']['name'],'bold')+ '\n\n' + key['currentItem']['description']
|
info[channel]['plot'] = support.typo(key['currentItem']['name'],'bold')+ '\n\n' + key['currentItem']['description']
|
||||||
|
for key in json:
|
||||||
for i, key in enumerate(json):
|
|
||||||
channel = key['channel']
|
channel = key['channel']
|
||||||
itemlist.append(item.clone(title = support.typo(channel, 'bold'), fulltitle = channel, show = channel, url = key['video']['contentUrl'],
|
livedict[channel] = {}
|
||||||
thumbnail = key['transparent-icon'].replace("[RESOLUTION]", "256x-"), forcethumb = True , fanart = info[channel]['fanart'],
|
livedict[channel]['url'] = key['video']['content_url']
|
||||||
plot = info[channel]['plot'], action = 'play', no_return=True))
|
livedict[channel]['plot'] = info[channel]['plot']
|
||||||
|
livedict[channel]['fanart'] = info[channel]['fanart']
|
||||||
|
|
||||||
|
return livedict
|
||||||
|
|
||||||
|
|
||||||
|
def live(item):
|
||||||
|
support.info()
|
||||||
|
itemlist =[]
|
||||||
|
for channel, value in liveDict().items():
|
||||||
|
itemlist.append(item.clone(title = support.typo(channel, 'bold'), fulltitle = channel, show = channel, url = value['url'],
|
||||||
|
plot = value['plot'], action = 'play', fanart = value['fanart'], no_return=True))
|
||||||
return support.thumb(itemlist, live=True)
|
return support.thumb(itemlist, live=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -331,7 +342,7 @@ def findvideos(item):
|
|||||||
else:
|
else:
|
||||||
url = item.url
|
url = item.url
|
||||||
|
|
||||||
itemlist.append(item.clone(server = 'directo', title = support.config.get_localized_string(30137), fanart = item.json, url = getUrl(url), action = 'play' ))
|
itemlist.append(item.clone(server = 'directo', title = 'Rai Play', url = getUrl(url) + '&output=56', action = 'play'))
|
||||||
return support.server(item, itemlist=itemlist, Download=False)
|
return support.server(item, itemlist=itemlist, Download=False)
|
||||||
|
|
||||||
|
|
||||||
@@ -350,7 +361,7 @@ def getUrl(pathId):
|
|||||||
if url.endswith(".html?json"):
|
if url.endswith(".html?json"):
|
||||||
url = url.replace(".html?json", ".json")
|
url = url.replace(".html?json", ".json")
|
||||||
elif url.endswith("/?json"):
|
elif url.endswith("/?json"):
|
||||||
url = url.replace("/?json","/index.json")
|
url = url.replace("/?json",".json")
|
||||||
elif url.endswith("?json"):
|
elif url.endswith("?json"):
|
||||||
url = url.replace("?json",".json")
|
url = url.replace("?json",".json")
|
||||||
|
|
||||||
@@ -407,3 +418,16 @@ def load_episodes(key, item):
|
|||||||
return itemlist
|
return itemlist
|
||||||
|
|
||||||
|
|
||||||
|
def play(item):
|
||||||
|
if item.livefilter:
|
||||||
|
d = liveDict()
|
||||||
|
item = item.clone(server='directo', fulltitle=item.livefilter, url=d[item.livefilter]['url'], plot=d[item.livefilter]['plot'], forcethumb=True, no_return=True)
|
||||||
|
support.thumb(item, live=True)
|
||||||
|
if '&output=56' in item.url:
|
||||||
|
match = support.match(item, patron=r'content"><!\[CDATA\[([^\]]+)(?:.*?"WIDEVINE","licenceUrl":"([^"]+))?').match
|
||||||
|
item.url = match[0]
|
||||||
|
if len(match) == 2:
|
||||||
|
item.drm = 'com.widevine.alpha'
|
||||||
|
item.license = match[1] + '|' + host + '|R{SSM}|'
|
||||||
|
logger.debug('PLAY URL', item.url)
|
||||||
|
return [item]
|
||||||
+3
-3
@@ -69,13 +69,13 @@ def peliculas(item):
|
|||||||
|
|
||||||
if item.args == 'last':
|
if item.args == 'last':
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
patron = r'singleUpdate">[^>]+>[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>\s*<h2>(?P<title>[^<]+)<[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>\s*<a href="(?P<url>[^"]+)">[^>]+>[^>]+>[^>]+>\s*(?P<season>\d+)\D+(?P<episode>\d+)(?:[^\(]*\()?(?P<lang>[^\)]+)?(?:\))?'
|
patron = r'singleUpdate">(?:[^>]+>){2}\s*<img src="(?P<thumb>[^"]+)"(?:[^>]+>){3}\s*<h2>(?P<title>[^<]+)<(?:[^>]+>){14,16}\s*<a href="(?P<url>[^"]+)">(?:[^>]+>){3}\s*(?P<season>\d+)\D+(?P<episode>\d+)(?:[^\(]*\()?(?P<lang>[^\)]+)?(?:\))?'
|
||||||
elif item.args == 'best':
|
elif item.args == 'best':
|
||||||
action='episodios'
|
action='episodios'
|
||||||
patron = r'col-md-3">\s*<a href="(?P<url>[^"]+)">[^>]+>\s*<div class="infoVetrina">[^>]+>(?P<year>\d{4})[^>]+>[^>]+>(?P<title>[^<]+)<[^>]+>[^>]+>[^>]+>[^>]+>(?P<rating>[^<]+)[^>]+>[^>]+>[^>]+>[^>]+>\s*<img src="(?P<thumb>[^"]+)"'
|
patron = r'col-md-3">\s*<a href="(?P<url>[^"]+)">[^>]+>\s*<div class="infoVetrina">[^>]+>(?P<year>\d{4})(?:[^>]+>){2}(?P<title>[^<]+)<(?:[^>]+>){4}(?P<rating>[^<]+)(?:[^>]+>){4}\s*<img src="(?P<thumb>[^"]+)"'
|
||||||
else:
|
else:
|
||||||
action='episodios'
|
action='episodios'
|
||||||
patron = r'<a href="(?P<url>[^"]+)">[^>]+>\s*<div class="infoSeries">\s*<h2>(?P<title>[^<]+)<[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<rating>[^<]+)?[^>]+>[^>]+>[^>]+>\s*<img src="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>(?P<quality>[^<]+)<[^>]+>[^>]+>(?P<year>\d{4})'
|
patron = r'<a href="(?P<url>[^"]+)">[^>]+>\s*<div class="infoSeries">\s*<h2>(?P<title>[^<]+)<(?:[^>]+>){5}(?P<rating>[^<]+)?(?:[^>]+>){3}\s*<img src="(?P<thumb>[^"]+)"(?:[^>]+>){3}(?P<quality>[^<]+)<(?:[^>]+>){2}(?P<year>\d{4})'
|
||||||
patronNext=r'next page-numbers" href="([^"]+)"'
|
patronNext=r'next page-numbers" href="([^"]+)"'
|
||||||
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ def mainlist(item):
|
|||||||
|
|
||||||
|
|
||||||
film = ['/ultimi-film-aggiunti/',
|
film = ['/ultimi-film-aggiunti/',
|
||||||
('Lista', ['/lista-film/', 'peliculas', 'lista'])
|
('A-Z', ['/lista-film/', 'peliculas', 'lista'])
|
||||||
]
|
]
|
||||||
|
|
||||||
tvshow = ['',
|
tvshow = ['',
|
||||||
@@ -65,7 +65,7 @@ def peliculas(item):
|
|||||||
|
|
||||||
if item.args == 'search':
|
if item.args == 'search':
|
||||||
patronBlock = r'>Lista Serie Tv</a></li></ul></div><div id="box_movies">(?P<block>.*?)<div id="paginador">'
|
patronBlock = r'>Lista Serie Tv</a></li></ul></div><div id="box_movies">(?P<block>.*?)<div id="paginador">'
|
||||||
patron = r'<div class="movie">[^>]+[^>]+>\s?<img src="(?P<thumb>[^"]+)" alt="(?P<title>.+?)\s?(?P<year>[\d\-]+)?"[^>]+>\s?<a href="(?P<url>[^"]+)">'
|
patron = r'<div class="movie">[^>]+[^>]+>\s*<img src="(?P<thumb>[^"]+)" alt="(?P<title>.+?)(?:(?P<year>\d{4})|")[^>]*>\s*<a href="([^"]+)'
|
||||||
elif item.contentType == 'episode':
|
elif item.contentType == 'episode':
|
||||||
pagination = 35
|
pagination = 35
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
@@ -85,7 +85,7 @@ def peliculas(item):
|
|||||||
pagination = 25
|
pagination = 25
|
||||||
|
|
||||||
if item.args == 'lista':
|
if item.args == 'lista':
|
||||||
patron = r'href="(?P<url>[^"]+)"[^>]+>(?P<title>.*?)(?P<year>\d{4})?<'
|
patron = r'href="(?P<url>[^"]+)"[^>]+>(?P<title>.+?)(?:\s(?P<year>\d{4})|<)'
|
||||||
patronBlock = r'Lista dei film disponibili in streaming e anche in download\.</p>(?P<block>.*?)<div class="footer_c">'
|
patronBlock = r'Lista dei film disponibili in streaming e anche in download\.</p>(?P<block>.*?)<div class="footer_c">'
|
||||||
else:
|
else:
|
||||||
patron = r'<tr><td><a href="(?P<url>[^"]+)"(?:|.+?)?>(?: )?[ ]?(?P<title>.*?)[ ]?(?P<quality>HD)?[ ]?(?P<year>\d+)?(?: | HD | Streaming | MD(?: iSTANCE)? )?</a>'
|
patron = r'<tr><td><a href="(?P<url>[^"]+)"(?:|.+?)?>(?: )?[ ]?(?P<title>.*?)[ ]?(?P<quality>HD)?[ ]?(?P<year>\d+)?(?: | HD | Streaming | MD(?: iSTANCE)? )?</a>'
|
||||||
|
|||||||
+23
-18
@@ -291,25 +291,30 @@ def newest(categoria):
|
|||||||
def search(item, texto):
|
def search(item, texto):
|
||||||
info(texto)
|
info(texto)
|
||||||
itemlist = []
|
itemlist = []
|
||||||
|
try:
|
||||||
patron = r'<li class="cat-item cat-item-\d+"><a href="([^"]+)"\s?>([^<]+)</a>'
|
patron = r'<li class="cat-item cat-item-\d+"><a href="([^"]+)"\s?>([^<]+)</a>'
|
||||||
matches = support.match(item, patron=patron, headers=headers).matches
|
matches = support.match(item, patron=patron, headers=headers).matches
|
||||||
for i, (scrapedurl, scrapedtitle) in enumerate(matches):
|
for i, (scrapedurl, scrapedtitle) in enumerate(matches):
|
||||||
if texto.upper() in scrapedtitle.upper():
|
if texto.upper() in scrapedtitle.upper():
|
||||||
scrapedthumbnail = ""
|
scrapedthumbnail = ""
|
||||||
scrapedplot = ""
|
scrapedplot = ""
|
||||||
title = cleantitle(scrapedtitle)
|
title = cleantitle(scrapedtitle)
|
||||||
itemlist.append(
|
itemlist.append(
|
||||||
item.clone(action="episodios",
|
item.clone(action="episodios",
|
||||||
title=title,
|
title=title,
|
||||||
url=scrapedurl,
|
url=scrapedurl,
|
||||||
thumbnail=scrapedthumbnail,
|
thumbnail=scrapedthumbnail,
|
||||||
fulltitle=title,
|
fulltitle=title,
|
||||||
show=title,
|
show=title,
|
||||||
plot=scrapedplot,
|
plot=scrapedplot,
|
||||||
contentType='episode',
|
contentType='episode',
|
||||||
originalUrl=scrapedurl))
|
originalUrl=scrapedurl))
|
||||||
tmdb.set_infoLabels_itemlist(itemlist, seekTmdb=True)
|
tmdb.set_infoLabels_itemlist(itemlist, seekTmdb=True)
|
||||||
|
except:
|
||||||
|
import sys
|
||||||
|
for line in sys.exc_info():
|
||||||
|
support.info('search log:', line)
|
||||||
|
return []
|
||||||
|
|
||||||
return itemlist
|
return itemlist
|
||||||
|
|
||||||
|
|||||||
@@ -33,14 +33,15 @@ def mainlist(item):
|
|||||||
|
|
||||||
@support.scrape
|
@support.scrape
|
||||||
def peliculas(item):
|
def peliculas(item):
|
||||||
|
# debug=True
|
||||||
patronBlock = r'<div class="wrap">\s*<h.>.*?</h.>(?P<block>.*?)<footer>'
|
patronBlock = r'<div class="wrap">\s*<h.>.*?</h.>(?P<block>.*?)<footer>'
|
||||||
|
|
||||||
if item.args != 'update':
|
if item.args != 'update':
|
||||||
action = 'episodios'
|
action = 'episodios'
|
||||||
patron = r'<div class="item">\s*<a href="(?P<url>[^"]+)" data-original="(?P<thumb>[^"]+)" class="lazy inner">[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>[^<]+)<'
|
patron = r'<div class="item">\s*?<a href="(?P<url>[^"]+)" data-original="(?P<thumb>[^"]+)" class="lazy inner">(?:[^>]+>){4}(?P<title>[^<]+)<'
|
||||||
else:
|
else:
|
||||||
action = 'findvideos'
|
action = 'findvideos'
|
||||||
patron = r'<div class="item">\s+?<a href="(?P<url>[^"]+)"\s+?data-original="(?P<thumb>[^"]+)"[^>]+>[^>]+>[^>]+>[^>]+>[^>]+>(?P<title>.+?)<[^>]+>\((?P<episode>[\dx\-]+)\s+?(?P<lang>Sub-Ita|[iITtAa]+)\)<'
|
patron = r'<div class="item">\s*?<a href="(?P<url>[^"]+)"\s*?data-original="(?P<thumb>[^"]+)"(?:[^>]+>){5}(?P<title>.+?)<[^>]+>\((?P<episode>[\dx\-]+)\s+?(?P<lang>Sub-Ita|[iITtAa]+)\)<'
|
||||||
pagination = 25
|
pagination = 25
|
||||||
|
|
||||||
patronNext = r'<li><a href="([^"]+)"\s+?>Pagina successiva'
|
patronNext = r'<li><a href="([^"]+)"\s+?>Pagina successiva'
|
||||||
|
|||||||
+2
-1
@@ -220,7 +220,8 @@ def make_itemlist(itemlist, item, data):
|
|||||||
for key in data['data']:
|
for key in data['data']:
|
||||||
if search.lower() in encode(key['title']).lower():
|
if search.lower() in encode(key['title']).lower():
|
||||||
infoLabels['year'] = key['date_published']
|
infoLabels['year'] = key['date_published']
|
||||||
infoLabels['title'] = infoLabels['tvshowtitle'] = key['title']
|
infoLabels['title'] = key['title']
|
||||||
|
if item.contentType != 'movie': infoLabels['tvshowtitle'] = key['title']
|
||||||
title = encode(key['title'])
|
title = encode(key['title'])
|
||||||
itemlist.append(
|
itemlist.append(
|
||||||
item.clone(title = support.typo(title, 'bold'),
|
item.clone(title = support.typo(title, 'bold'),
|
||||||
|
|||||||
+22
-1
@@ -6,7 +6,28 @@ import sys
|
|||||||
# Appends the main plugin dir to the PYTHONPATH if an internal package cannot be imported.
|
# Appends the main plugin dir to the PYTHONPATH if an internal package cannot be imported.
|
||||||
# Examples: In Plex Media Server all modules are under "Code.*" package, and in Enigma2 under "Plugins.Extensions.*"
|
# Examples: In Plex Media Server all modules are under "Code.*" package, and in Enigma2 under "Plugins.Extensions.*"
|
||||||
try:
|
try:
|
||||||
# from core import logger
|
|
||||||
import core
|
import core
|
||||||
except:
|
except:
|
||||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
|
||||||
|
# Connect to database
|
||||||
|
from . import filetools
|
||||||
|
from platformcode import config
|
||||||
|
from collections import defaultdict
|
||||||
|
from lib.sqlitedict import SqliteDict
|
||||||
|
|
||||||
|
|
||||||
|
class nested_dict_sqlite(defaultdict):
|
||||||
|
'like defaultdict but default_factory receives the key'
|
||||||
|
|
||||||
|
def __missing__(self, key):
|
||||||
|
self[key] = value = self.default_factory(key)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
for key in self.keys():
|
||||||
|
self[key].close()
|
||||||
|
|
||||||
|
|
||||||
|
db_name = filetools.join(config.get_data_path(), "db.sqlite")
|
||||||
|
db = nested_dict_sqlite(lambda table: SqliteDict(db_name, table, 'c', True))
|
||||||
|
|||||||
+1
-1
@@ -419,7 +419,7 @@ def downloadpage(url, **opt):
|
|||||||
response['data'] = response['data'].decode('ISO-8859-1')
|
response['data'] = response['data'].decode('ISO-8859-1')
|
||||||
|
|
||||||
if req.headers.get('Server', '').startswith('cloudflare') and response_code in [429, 503, 403]\
|
if req.headers.get('Server', '').startswith('cloudflare') and response_code in [429, 503, 403]\
|
||||||
and not opt.get('CF', False) and 'Please turn JavaScript on and reload the page' in response['data']:
|
and not opt.get('CF', False) and 'Ray ID' in response['data'] and not opt.get('post', None):
|
||||||
logger.debug("CF retry... for domain: %s" % domain)
|
logger.debug("CF retry... for domain: %s" % domain)
|
||||||
from lib import proxytranslate
|
from lib import proxytranslate
|
||||||
gResp = proxytranslate.process_request_proxy(url)
|
gResp = proxytranslate.process_request_proxy(url)
|
||||||
|
|||||||
+7
-27
@@ -12,16 +12,11 @@ else:
|
|||||||
|
|
||||||
from lib.requests_toolbelt.adapters import host_header_ssl
|
from lib.requests_toolbelt.adapters import host_header_ssl
|
||||||
from lib import doh
|
from lib import doh
|
||||||
from platformcode import logger, config
|
from platformcode import logger
|
||||||
import requests
|
import requests
|
||||||
from core import scrapertools
|
from core import scrapertools
|
||||||
|
from core import db
|
||||||
|
|
||||||
try:
|
|
||||||
import _sqlite3 as sql
|
|
||||||
except:
|
|
||||||
import sqlite3 as sql
|
|
||||||
|
|
||||||
db = os.path.join(config.get_data_path(), 'kod_db.sqlite')
|
|
||||||
if 'PROTOCOL_TLS' in ssl.__dict__:
|
if 'PROTOCOL_TLS' in ssl.__dict__:
|
||||||
protocol = ssl.PROTOCOL_TLS
|
protocol = ssl.PROTOCOL_TLS
|
||||||
elif 'PROTOCOL_SSLv23' in ssl.__dict__:
|
elif 'PROTOCOL_SSLv23' in ssl.__dict__:
|
||||||
@@ -48,8 +43,6 @@ class CustomContext(ssl.SSLContext):
|
|||||||
class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
||||||
|
|
||||||
def __init__(self, domain, CF=False, *args, **kwargs):
|
def __init__(self, domain, CF=False, *args, **kwargs):
|
||||||
self.conn = sql.connect(db)
|
|
||||||
self.cur = self.conn.cursor()
|
|
||||||
self.ssl_context = CustomContext(protocol, domain)
|
self.ssl_context = CustomContext(protocol, domain)
|
||||||
self.CF = CF # if cloudscrape is in action
|
self.CF = CF # if cloudscrape is in action
|
||||||
self.cipherSuite = kwargs.pop('cipherSuite', DEFAULT_CIPHERS)
|
self.cipherSuite = kwargs.pop('cipherSuite', DEFAULT_CIPHERS)
|
||||||
@@ -57,18 +50,13 @@ class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
|||||||
super(CipherSuiteAdapter, self).__init__(**kwargs)
|
super(CipherSuiteAdapter, self).__init__(**kwargs)
|
||||||
|
|
||||||
def flushDns(self, request, domain, **kwargs):
|
def flushDns(self, request, domain, **kwargs):
|
||||||
self.cur.execute('delete from dnscache where domain=?', (domain,))
|
del db['dnscache'][domain]
|
||||||
self.conn.commit()
|
|
||||||
return self.send(request, flushedDns=True, **kwargs)
|
return self.send(request, flushedDns=True, **kwargs)
|
||||||
|
|
||||||
def getIp(self, domain):
|
def getIp(self, domain):
|
||||||
ip = None
|
ip = db['dnscache'].get(domain, None)
|
||||||
try:
|
logger.info('Cache DNS: ' + domain + ' = ' + str(ip))
|
||||||
self.cur.execute('select ip from dnscache where domain=?', (domain,))
|
|
||||||
ip = self.cur.fetchall()[0][0]
|
|
||||||
logger.info('Cache DNS: ' + domain + ' = ' + str(ip))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if not ip: # not cached
|
if not ip: # not cached
|
||||||
try:
|
try:
|
||||||
ip = doh.query(domain)[0]
|
ip = doh.query(domain)[0]
|
||||||
@@ -81,15 +69,7 @@ class CipherSuiteAdapter(host_header_ssl.HostHeaderSSLAdapter):
|
|||||||
return ip
|
return ip
|
||||||
|
|
||||||
def writeToCache(self, domain, ip):
|
def writeToCache(self, domain, ip):
|
||||||
try:
|
db['dnscache'][domain] = ip
|
||||||
self.cur.execute('insert into dnscache values(?,?)', (domain, ip))
|
|
||||||
except:
|
|
||||||
self.cur.execute("""CREATE TABLE IF NOT EXISTS dnscache(
|
|
||||||
"domain" TEXT NOT NULL UNIQUE,
|
|
||||||
"ip" TEXT NOT NULL,
|
|
||||||
PRIMARY KEY("domain")
|
|
||||||
);""")
|
|
||||||
self.conn.commit()
|
|
||||||
|
|
||||||
def init_poolmanager(self, *args, **kwargs):
|
def init_poolmanager(self, *args, **kwargs):
|
||||||
kwargs['ssl_context'] = self.ssl_context
|
kwargs['ssl_context'] = self.ssl_context
|
||||||
|
|||||||
+7
-5
@@ -32,19 +32,21 @@ def find_and_set_infoLabels(item):
|
|||||||
|
|
||||||
# Get the default Scraper of the configuration according to the content type
|
# Get the default Scraper of the configuration according to the content type
|
||||||
if item.contentType == "movie":
|
if item.contentType == "movie":
|
||||||
scraper_actual = ['tmdb'][config.get_setting("scraper_movies", "videolibrary")]
|
scraper_actual = 'tmdb'
|
||||||
|
# scraper_actual = ['tmdb'][config.get_setting("scraper_movies", "videolibrary")]
|
||||||
tipo_contenido = "movie"
|
tipo_contenido = "movie"
|
||||||
title = item.contentTitle
|
title = item.contentTitle
|
||||||
# Complete list of options for this type of content
|
# Complete list of options for this type of content
|
||||||
list_opciones_cuadro.append(scrapers_disponibles['tmdb'])
|
list_opciones_cuadro.append(scrapers_disponibles['tmdb'])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
scraper_actual = ['tmdb', 'tvdb'][config.get_setting("scraper_tvshows", "videolibrary")]
|
scraper_actual = 'tmdb'
|
||||||
|
# scraper_actual = ['tmdb', 'tvdb'][config.get_setting("scraper_tvshows", "videolibrary")]
|
||||||
tipo_contenido = "serie"
|
tipo_contenido = "serie"
|
||||||
title = item.contentSerieName
|
title = item.contentSerieName
|
||||||
# Complete list of options for this type of content
|
# Complete list of options for this type of content
|
||||||
list_opciones_cuadro.append(scrapers_disponibles['tmdb'])
|
list_opciones_cuadro.append(scrapers_disponibles['tmdb'])
|
||||||
list_opciones_cuadro.append(scrapers_disponibles['tvdb'])
|
# list_opciones_cuadro.append(scrapers_disponibles['tvdb'])
|
||||||
|
|
||||||
# We import the scraper
|
# We import the scraper
|
||||||
try:
|
try:
|
||||||
@@ -187,7 +189,7 @@ def callback_cuadro_completar(item, dict_values):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_nfo(item):
|
def get_nfo(item, search_groups=False):
|
||||||
"""
|
"""
|
||||||
Returns the information necessary for the result to be scraped into the kodi video library,
|
Returns the information necessary for the result to be scraped into the kodi video library,
|
||||||
|
|
||||||
@@ -229,7 +231,7 @@ def get_nfo(item):
|
|||||||
if item.contentType == "movie": scraper_actual = ['tmdb'][config.get_setting("scraper_movies", "videolibrary")]
|
if item.contentType == "movie": scraper_actual = ['tmdb'][config.get_setting("scraper_movies", "videolibrary")]
|
||||||
else: scraper_actual = ['tmdb', 'tvdb'][config.get_setting("scraper_tvshows", "videolibrary")]
|
else: scraper_actual = ['tmdb', 'tvdb'][config.get_setting("scraper_tvshows", "videolibrary")]
|
||||||
scraper = __import__('core.%s' % scraper_actual, fromlist=["core.%s" % scraper_actual])
|
scraper = __import__('core.%s' % scraper_actual, fromlist=["core.%s" % scraper_actual])
|
||||||
return scraper.get_nfo(item)
|
return scraper.get_nfo(item, search_groups)
|
||||||
|
|
||||||
|
|
||||||
def sort_episode_list(episodelist):
|
def sort_episode_list(episodelist):
|
||||||
|
|||||||
+1
-1
@@ -1367,7 +1367,7 @@ def thumb(item_itemlist_string=None, genre=False, live=False):
|
|||||||
for item in item_itemlist_string:
|
for item in item_itemlist_string:
|
||||||
item.thumbnail = "https://raw.githubusercontent.com/kodiondemand/media/master/live/" + item.fulltitle.lower().replace(' ','_') + '.png'
|
item.thumbnail = "https://raw.githubusercontent.com/kodiondemand/media/master/live/" + item.fulltitle.lower().replace(' ','_') + '.png'
|
||||||
else:
|
else:
|
||||||
item_itemlist_string.thumbnail = "https://raw.githubusercontent.com/kodiondemand/media/master/live/" + item.fulltitle.lower().replace(' ','_') + '.png'
|
item_itemlist_string.thumbnail = "https://raw.githubusercontent.com/kodiondemand/media/master/live/" + item_itemlist_string.fulltitle.lower().replace(' ','_') + '.png'
|
||||||
return item_itemlist_string
|
return item_itemlist_string
|
||||||
|
|
||||||
icon_dict = {'movie':['film', 'movie'],
|
icon_dict = {'movie':['film', 'movie'],
|
||||||
|
|||||||
+67
-48
@@ -3,7 +3,8 @@
|
|||||||
# from future import standard_library
|
# from future import standard_library
|
||||||
# standard_library.install_aliases()
|
# standard_library.install_aliases()
|
||||||
# from builtins import str
|
# from builtins import str
|
||||||
import sys
|
import datetime
|
||||||
|
import sys, requests
|
||||||
PY3 = False
|
PY3 = False
|
||||||
if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
|
if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ else:
|
|||||||
from future.builtins import range
|
from future.builtins import range
|
||||||
from future.builtins import object
|
from future.builtins import object
|
||||||
|
|
||||||
import ast, copy, re, sqlite3, time, xbmcaddon
|
import ast, copy, re, time
|
||||||
|
|
||||||
from core import filetools, httptools, jsontools, scrapertools
|
from core import filetools, httptools, jsontools, scrapertools
|
||||||
from core.item import InfoLabels
|
from core.item import InfoLabels
|
||||||
@@ -62,27 +63,7 @@ def_lang = info_language[config.get_setting("info_language", "videolibrary")]
|
|||||||
# ------------------------------------------------- -------------------------------------------------- -----------
|
# ------------------------------------------------- -------------------------------------------------- -----------
|
||||||
|
|
||||||
otmdb_global = None
|
otmdb_global = None
|
||||||
fname = filetools.join(config.get_data_path(), "kod_db.sqlite")
|
from core import db
|
||||||
|
|
||||||
def create_bd():
|
|
||||||
conn = sqlite3.connect(fname)
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('CREATE TABLE IF NOT EXISTS tmdb_cache (url TEXT PRIMARY KEY, response TEXT, added TEXT)')
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def drop_bd():
|
|
||||||
conn = sqlite3.connect(fname)
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute('DROP TABLE IF EXISTS tmdb_cache')
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
create_bd()
|
|
||||||
|
|
||||||
|
|
||||||
# The function name is the name of the decorator and receives the function that decorates.
|
# The function name is the name of the decorator and receives the function that decorates.
|
||||||
@@ -93,17 +74,11 @@ def cache_response(fn):
|
|||||||
# start_time = time.time()
|
# start_time = time.time()
|
||||||
|
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
import base64
|
def check_expired(saved_date):
|
||||||
|
|
||||||
def check_expired(ts):
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
valided = False
|
valided = False
|
||||||
|
|
||||||
cache_expire = config.get_setting("tmdb_cache_expire", default=0)
|
cache_expire = config.get_setting("tmdb_cache_expire", default=0)
|
||||||
|
current_date = datetime.datetime.now()
|
||||||
saved_date = datetime.datetime.fromtimestamp(ts)
|
|
||||||
current_date = datetime.datetime.fromtimestamp(time.time())
|
|
||||||
elapsed = current_date - saved_date
|
elapsed = current_date - saved_date
|
||||||
|
|
||||||
# 1 day
|
# 1 day
|
||||||
@@ -148,29 +123,18 @@ def cache_response(fn):
|
|||||||
result = fn(*args)
|
result = fn(*args)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
conn = sqlite3.connect(fname, timeout=15)
|
|
||||||
c = conn.cursor()
|
|
||||||
url = re.sub('&year=-', '', args[0])
|
url = re.sub('&year=-', '', args[0])
|
||||||
if PY3: url = str.encode(url)
|
if PY3: url = str.encode(url)
|
||||||
url_base64 = base64.b64encode(url)
|
|
||||||
c.execute("SELECT response, added FROM tmdb_cache WHERE url=?", (url_base64,))
|
|
||||||
row = c.fetchone()
|
|
||||||
|
|
||||||
if row and check_expired(float(row[1])):
|
row = db['tmdb_cache'].get(url)
|
||||||
result = eval(base64.b64decode(row[0]))
|
|
||||||
|
if row and check_expired(row[1]):
|
||||||
|
result = row[0]
|
||||||
|
|
||||||
# si no se ha obtenido información, llamamos a la funcion
|
# si no se ha obtenido información, llamamos a la funcion
|
||||||
if not result:
|
if not result:
|
||||||
result = fn(*args)
|
result = fn(*args)
|
||||||
result = str(result)
|
db['tmdb_cache'][url] = [result, datetime.datetime.now()]
|
||||||
if PY3: result = str.encode(result)
|
|
||||||
result_base64 = base64.b64encode(result)
|
|
||||||
c.execute("INSERT OR REPLACE INTO tmdb_cache (url, response, added) VALUES (?, ?, ?)",
|
|
||||||
(url_base64, result_base64, time.time()))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
# elapsed_time = time.time() - start_time
|
# elapsed_time = time.time() - start_time
|
||||||
# logger.debug("TARDADO %s" % elapsed_time)
|
# logger.debug("TARDADO %s" % elapsed_time)
|
||||||
@@ -550,7 +514,7 @@ def find_and_set_infoLabels(item):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_nfo(item):
|
def get_nfo(item, search_groups=False):
|
||||||
"""
|
"""
|
||||||
Returns the information necessary for the result to be scraped into the kodi video library, for tmdb it works only by passing it the url.
|
Returns the information necessary for the result to be scraped into the kodi video library, for tmdb it works only by passing it the url.
|
||||||
@param item: element that contains the data necessary to generate the info
|
@param item: element that contains the data necessary to generate the info
|
||||||
@@ -558,6 +522,23 @@ def get_nfo(item):
|
|||||||
@rtype: str
|
@rtype: str
|
||||||
@return:
|
@return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if search_groups:
|
||||||
|
from platformcode.autorenumber import RENUMBER, GROUP
|
||||||
|
path = filetools.join(config.get_data_path(), "settings_channels", item.channel + "_data.json")
|
||||||
|
if filetools.exists(path):
|
||||||
|
g = jsontools.load(filetools.read(path)).get(RENUMBER,{}).get(item.fulltitle.strip(),{}).get(GROUP,'')
|
||||||
|
if g: return g + '\n'
|
||||||
|
|
||||||
|
groups = get_groups(item)
|
||||||
|
|
||||||
|
if groups:
|
||||||
|
Id = select_group(groups)
|
||||||
|
if Id:
|
||||||
|
info_nfo = 'https://www.themoviedb.org/tv/{}/episode_group/{}\n'.format(item.infoLabels['tmdb_id'], Id)
|
||||||
|
return info_nfo
|
||||||
|
else: return
|
||||||
|
|
||||||
if "season" in item.infoLabels and "episode" in item.infoLabels:
|
if "season" in item.infoLabels and "episode" in item.infoLabels:
|
||||||
info_nfo = "https://www.themoviedb.org/tv/%s/season/%s/episode/%s\n" % (item.infoLabels['tmdb_id'], item.contentSeason, item.contentEpisodeNumber)
|
info_nfo = "https://www.themoviedb.org/tv/%s/season/%s/episode/%s\n" % (item.infoLabels['tmdb_id'], item.contentSeason, item.contentEpisodeNumber)
|
||||||
else:
|
else:
|
||||||
@@ -565,6 +546,34 @@ def get_nfo(item):
|
|||||||
|
|
||||||
return info_nfo
|
return info_nfo
|
||||||
|
|
||||||
|
def get_groups(item):
|
||||||
|
url = 'https://api.themoviedb.org/3/tv/{}/episode_groups?api_key=a1ab8b8669da03637a4b98fa39c39228&language={}'.format(item.infoLabels['tmdb_id'], def_lang)
|
||||||
|
groups = requests.get(url).json().get('results',[])
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def select_group(groups):
|
||||||
|
selected = -1
|
||||||
|
selections = []
|
||||||
|
ids = []
|
||||||
|
for group in groups:
|
||||||
|
name = '[B]{}[/B] Seasons: {} Episodes: {}'.format(group.get('name',''), group.get('group_count',''), group.get('episode_count',''))
|
||||||
|
description = group.get('description','')
|
||||||
|
if description:
|
||||||
|
name = '{}\n{}'.format(name, description)
|
||||||
|
ID = group.get('id','')
|
||||||
|
if ID:
|
||||||
|
selections.append(name)
|
||||||
|
ids.append(ID)
|
||||||
|
if selections and ids:
|
||||||
|
selected = platformtools.dialog_select_group(config.get_localized_string(70831), selections)
|
||||||
|
if selected > -1:
|
||||||
|
return ids[selected]
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def get_group(Id):
|
||||||
|
url = 'https://api.themoviedb.org/3/tv/episode_group/{}?api_key=a1ab8b8669da03637a4b98fa39c39228&language={}'.format(Id, def_lang)
|
||||||
|
group = requests.get(url).json().get('groups',[])
|
||||||
|
return group
|
||||||
|
|
||||||
def completar_codigos(item):
|
def completar_codigos(item):
|
||||||
"""
|
"""
|
||||||
@@ -1009,6 +1018,11 @@ class Tmdb(object):
|
|||||||
% (buscando, len(results), page, index_results))
|
% (buscando, len(results), page, index_results))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
# We sort result based on fuzzy match to detect most similar
|
||||||
|
if len(results) > 1:
|
||||||
|
from lib.fuzzy_match import algorithims
|
||||||
|
results.sort(key=lambda r: algorithims.trigram(text_simple, r['title'] if self.busqueda_tipo == 'movie' else r['name']), reverse=True)
|
||||||
|
|
||||||
# We return the number of results of this page
|
# We return the number of results of this page
|
||||||
self.results = results
|
self.results = results
|
||||||
self.total_results = total_results
|
self.total_results = total_results
|
||||||
@@ -1430,6 +1444,11 @@ class Tmdb(object):
|
|||||||
|
|
||||||
return ret_dic
|
return ret_dic
|
||||||
|
|
||||||
|
def get_list_episodes(self):
|
||||||
|
url = 'https://api.themoviedb.org/3/tv/{id}?api_key=a1ab8b8669da03637a4b98fa39c39228&language={lang}'.format(id=self.busqueda_id, lang=self.busqueda_idioma)
|
||||||
|
results = requests.get(url).json().get('seasons', [])
|
||||||
|
return results if 'Error' not in results else []
|
||||||
|
|
||||||
def get_videos(self):
|
def get_videos(self):
|
||||||
"""
|
"""
|
||||||
:return: Returns an ordered list (language / resolution / type) of Dict objects in which each of its elements corresponds to a trailer, teaser or clip from youtube.
|
:return: Returns an ordered list (language / resolution / type) of Dict objects in which each of its elements corresponds to a trailer, teaser or clip from youtube.
|
||||||
|
|||||||
+1
-1
@@ -298,7 +298,7 @@ def set_infoLabels_item(item):
|
|||||||
return len(item.infoLabels)
|
return len(item.infoLabels)
|
||||||
|
|
||||||
|
|
||||||
def get_nfo(item):
|
def get_nfo(item, search_groups=False):
|
||||||
"""
|
"""
|
||||||
Returns the information necessary for the result to be scraped into the kodi video library,
|
Returns the information necessary for the result to be scraped into the kodi video library,
|
||||||
|
|
||||||
|
|||||||
+32
-27
@@ -14,6 +14,7 @@ from core import filetools, scraper, scrapertools
|
|||||||
from core.item import Item
|
from core.item import Item
|
||||||
from lib import generictools
|
from lib import generictools
|
||||||
from platformcode import config, logger, platformtools
|
from platformcode import config, logger, platformtools
|
||||||
|
from platformcode.autorenumber import RENUMBER
|
||||||
|
|
||||||
FOLDER_MOVIES = config.get_setting("folder_movies")
|
FOLDER_MOVIES = config.get_setting("folder_movies")
|
||||||
FOLDER_TVSHOWS = config.get_setting("folder_tvshows")
|
FOLDER_TVSHOWS = config.get_setting("folder_tvshows")
|
||||||
@@ -234,14 +235,14 @@ def update_renumber_options(item, head_nfo, path):
|
|||||||
if filetools.isfile(tvshow_path) and item.channel_prefs:
|
if filetools.isfile(tvshow_path) and item.channel_prefs:
|
||||||
for channel in item.channel_prefs:
|
for channel in item.channel_prefs:
|
||||||
filename = filetools.join(config.get_data_path(), "settings_channels", channel + '_data.json')
|
filename = filetools.join(config.get_data_path(), "settings_channels", channel + '_data.json')
|
||||||
|
if filetools.isfile(filename):
|
||||||
json_file = jsontools.load(filetools.read(filename))
|
json_file = jsontools.load(filetools.read(filename))
|
||||||
if 'TVSHOW_AUTORENUMBER' in json_file:
|
if RENUMBER in json_file:
|
||||||
json = json_file['TVSHOW_AUTORENUMBER']
|
json = json_file[RENUMBER]
|
||||||
if item.fulltitle in json:
|
if item.fulltitle in json:
|
||||||
item.channel_prefs[channel]['TVSHOW_AUTORENUMBER'] = json[item.fulltitle]
|
item.channel_prefs[channel][RENUMBER] = json[item.fulltitle]
|
||||||
logger.debug('UPDATED=\n' + str(item.channel_prefs))
|
logger.debug('UPDATED=\n' + str(item.channel_prefs))
|
||||||
filetools.write(tvshow_path, head_nfo + item.tojson())
|
filetools.write(tvshow_path, head_nfo + item.tojson())
|
||||||
|
|
||||||
def add_renumber_options(item, head_nfo, path):
|
def add_renumber_options(item, head_nfo, path):
|
||||||
from core import jsontools
|
from core import jsontools
|
||||||
@@ -249,8 +250,8 @@ def add_renumber_options(item, head_nfo, path):
|
|||||||
ret = None
|
ret = None
|
||||||
filename = filetools.join(config.get_data_path(), "settings_channels", item.channel + '_data.json')
|
filename = filetools.join(config.get_data_path(), "settings_channels", item.channel + '_data.json')
|
||||||
json_file = jsontools.load(filetools.read(filename))
|
json_file = jsontools.load(filetools.read(filename))
|
||||||
if 'TVSHOW_AUTORENUMBER' in json_file:
|
if RENUMBER in json_file:
|
||||||
json = json_file['TVSHOW_AUTORENUMBER']
|
json = json_file[RENUMBER]
|
||||||
if item.fulltitle in json:
|
if item.fulltitle in json:
|
||||||
ret = json[item.fulltitle]
|
ret = json[item.fulltitle]
|
||||||
return ret
|
return ret
|
||||||
@@ -258,11 +259,11 @@ def add_renumber_options(item, head_nfo, path):
|
|||||||
def check_renumber_options(item):
|
def check_renumber_options(item):
|
||||||
from platformcode.autorenumber import load, write
|
from platformcode.autorenumber import load, write
|
||||||
for key in item.channel_prefs:
|
for key in item.channel_prefs:
|
||||||
if 'TVSHOW_AUTORENUMBER' in item.channel_prefs[key]:
|
if RENUMBER in item.channel_prefs[key]:
|
||||||
item.channel = key
|
item.channel = key
|
||||||
json = load(item)
|
json = load(item)
|
||||||
if not json or item.fulltitle not in json:
|
if not json or item.fulltitle not in json:
|
||||||
json[item.fulltitle] = item.channel_prefs[key]['TVSHOW_AUTORENUMBER']
|
json[item.fulltitle] = item.channel_prefs[key][RENUMBER]
|
||||||
write(item, json)
|
write(item, json)
|
||||||
|
|
||||||
# head_nfo, tvshow_item = read_nfo(filetools.join(item.context[0]['nfo']))
|
# head_nfo, tvshow_item = read_nfo(filetools.join(item.context[0]['nfo']))
|
||||||
@@ -275,7 +276,7 @@ def filter_list(episodelist, action=None, path=None):
|
|||||||
# if xbmc.getCondVisibility('system.platform.windows') > 0: path = path.replace('smb:','').replace('/','\\')
|
# if xbmc.getCondVisibility('system.platform.windows') > 0: path = path.replace('smb:','').replace('/','\\')
|
||||||
channel_prefs = {}
|
channel_prefs = {}
|
||||||
lang_sel = quality_sel = show_title = channel =''
|
lang_sel = quality_sel = show_title = channel =''
|
||||||
# from core.support import dbg;dbg()
|
|
||||||
if action:
|
if action:
|
||||||
tvshow_path = filetools.join(path, "tvshow.nfo")
|
tvshow_path = filetools.join(path, "tvshow.nfo")
|
||||||
head_nfo, tvshow_item = read_nfo(tvshow_path)
|
head_nfo, tvshow_item = read_nfo(tvshow_path)
|
||||||
@@ -294,7 +295,7 @@ def filter_list(episodelist, action=None, path=None):
|
|||||||
|
|
||||||
renumber = add_renumber_options(episodelist[0], head_nfo, tvshow_path)
|
renumber = add_renumber_options(episodelist[0], head_nfo, tvshow_path)
|
||||||
if renumber:
|
if renumber:
|
||||||
channel_prefs['TVSHOW_AUTORENUMBER'] = renumber
|
channel_prefs[RENUMBER] = renumber
|
||||||
|
|
||||||
if action == 'get_seasons':
|
if action == 'get_seasons':
|
||||||
if 'favourite_language' not in channel_prefs:
|
if 'favourite_language' not in channel_prefs:
|
||||||
@@ -499,7 +500,9 @@ def save_tvshow(item, episodelist, silent=False):
|
|||||||
if not filetools.exists(tvshow_path):
|
if not filetools.exists(tvshow_path):
|
||||||
# We create tvshow.nfo, if it does not exist, with the head_nfo, series info and watched episode marks
|
# We create tvshow.nfo, if it does not exist, with the head_nfo, series info and watched episode marks
|
||||||
logger.debug("Creating tvshow.nfo: " + tvshow_path)
|
logger.debug("Creating tvshow.nfo: " + tvshow_path)
|
||||||
head_nfo = scraper.get_nfo(item)
|
head_nfo = scraper.get_nfo(item, search_groups=True)
|
||||||
|
if not head_nfo:
|
||||||
|
return 0, 0, 0, ''
|
||||||
item.infoLabels['mediatype'] = "tvshow"
|
item.infoLabels['mediatype'] = "tvshow"
|
||||||
item.infoLabels['title'] = item.contentSerieName
|
item.infoLabels['title'] = item.contentSerieName
|
||||||
item_tvshow = Item(title=item.contentSerieName, channel="videolibrary", action="get_seasons",
|
item_tvshow = Item(title=item.contentSerieName, channel="videolibrary", action="get_seasons",
|
||||||
@@ -1004,21 +1007,20 @@ def add_movie(item):
|
|||||||
# If you do it in "Enter another name", TMDB will automatically search for the new title
|
# If you do it in "Enter another name", TMDB will automatically search for the new title
|
||||||
# If you do it in "Complete Information", it partially changes to the new title, but does not search TMDB. We have to do it
|
# If you do it in "Complete Information", it partially changes to the new title, but does not search TMDB. We have to do it
|
||||||
# If the second screen is canceled, the variable "scraper_return" will be False. The user does not want to continue
|
# If the second screen is canceled, the variable "scraper_return" will be False. The user does not want to continue
|
||||||
|
|
||||||
item = generictools.update_title(item) # We call the method that updates the title with tmdb.find_and_set_infoLabels
|
item = generictools.update_title(item) # We call the method that updates the title with tmdb.find_and_set_infoLabels
|
||||||
#if item.tmdb_stat:
|
#if item.tmdb_stat:
|
||||||
# del item.tmdb_stat # We clean the status so that it is not recorded in the Video Library
|
# del item.tmdb_stat # We clean the status so that it is not recorded in the Video Library
|
||||||
# if item:
|
if item:
|
||||||
new_item = item.clone(action="findvideos")
|
new_item = item.clone(action="findvideos")
|
||||||
insertados, sobreescritos, fallidos, path = save_movie(new_item)
|
insertados, sobreescritos, fallidos, path = save_movie(new_item)
|
||||||
|
|
||||||
if fallidos == 0:
|
if fallidos == 0:
|
||||||
platformtools.dialog_ok(config.get_localized_string(30131),
|
platformtools.dialog_ok(config.get_localized_string(30131),
|
||||||
config.get_localized_string(30135) % new_item.contentTitle) # 'has been added to the video library'
|
config.get_localized_string(30135) % new_item.contentTitle) # 'has been added to the video library'
|
||||||
else:
|
else:
|
||||||
filetools.rmdirtree(path)
|
filetools.rmdirtree(path)
|
||||||
platformtools.dialog_ok(config.get_localized_string(30131),
|
platformtools.dialog_ok(config.get_localized_string(30131),
|
||||||
config.get_localized_string(60066) % new_item.contentTitle) # "ERROR, the movie has NOT been added to the video library")
|
config.get_localized_string(60066) % new_item.contentTitle) # "ERROR, the movie has NOT been added to the video library")
|
||||||
|
|
||||||
|
|
||||||
def add_tvshow(item, channel=None):
|
def add_tvshow(item, channel=None):
|
||||||
@@ -1100,7 +1102,10 @@ def add_tvshow(item, channel=None):
|
|||||||
magnet_caching = False
|
magnet_caching = False
|
||||||
insertados, sobreescritos, fallidos, path = save_tvshow(item, itemlist)
|
insertados, sobreescritos, fallidos, path = save_tvshow(item, itemlist)
|
||||||
|
|
||||||
if not insertados and not sobreescritos and not fallidos:
|
if not path:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif not insertados and not sobreescritos and not fallidos:
|
||||||
filetools.rmdirtree(path)
|
filetools.rmdirtree(path)
|
||||||
platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60067) % item.show)
|
platformtools.dialog_ok(config.get_localized_string(30131), config.get_localized_string(60067) % item.show)
|
||||||
logger.error("The string %s could not be added to the video library. Could not get any episode" % item.show)
|
logger.error("The string %s could not be added to the video library. Could not get any episode" % item.show)
|
||||||
|
|||||||
+2
-3
@@ -27,6 +27,5 @@ from platformcode import launcher
|
|||||||
|
|
||||||
if sys.argv[2] == "":
|
if sys.argv[2] == "":
|
||||||
launcher.start()
|
launcher.start()
|
||||||
launcher.run()
|
|
||||||
else:
|
launcher.run()
|
||||||
launcher.run()
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# __init__.py
|
||||||
|
|
||||||
|
# Version of the fuzzy-match package
|
||||||
|
__version__ = "0.0.1"
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# encoding: utf-8
|
||||||
|
import math
|
||||||
|
from math import floor, ceil
|
||||||
|
import re
|
||||||
|
from collections import Counter
|
||||||
|
# import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def find_ngrams(string, split_num=3):
|
||||||
|
"""
|
||||||
|
Slice string into ngrams.
|
||||||
|
Returns array of ngrams for the given string.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
text: the string to find ngrams for.
|
||||||
|
split_num: the length the ngrams should be. Defaults to 3 (trigrams).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not string:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
words = [' {} '.format(x) for x in re.split(r'\W+', str(string).lower()) if x.strip()]
|
||||||
|
|
||||||
|
ngrams = set()
|
||||||
|
|
||||||
|
for word in words:
|
||||||
|
for x in range(0, len(word) - split_num + 1):
|
||||||
|
ngrams.add(word[x:x+split_num])
|
||||||
|
|
||||||
|
return ngrams
|
||||||
|
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def trigram(text1, text2, split_num=3):
|
||||||
|
"""
|
||||||
|
Find the similarity between two strings using ngrams.
|
||||||
|
Returns float score value, 0.0 being completely different strings and 1.0 being equal strings.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
text1: main string to compare against.
|
||||||
|
text2: second string to compare to text1.
|
||||||
|
split_num: the length the ngrams should be. Defaults to 3 (trigrams).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ngrams1 = find_ngrams(text1, split_num)
|
||||||
|
ngrams2 = find_ngrams(text2, split_num)
|
||||||
|
|
||||||
|
num_unique = len(ngrams1 | ngrams2)
|
||||||
|
num_equal = len(ngrams1 & ngrams2)
|
||||||
|
|
||||||
|
score = round(float(num_equal) / float(num_unique), 6)
|
||||||
|
|
||||||
|
return score
|
||||||
|
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def cosine(text1, text2):
|
||||||
|
"""
|
||||||
|
Find the similarity between two strings using cosine vectors.
|
||||||
|
Returns float score value, 0.0 being completely different strings and 1.0 being equal strings.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
text1: main string to compare against.
|
||||||
|
text2: second string to compare to text1.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
vec1 = Counter(re.compile(r"\w+").findall(text1))
|
||||||
|
vec2 = Counter(re.compile(r"\w+").findall(text2))
|
||||||
|
intersection = set(vec1.keys()) & set(vec2.keys())
|
||||||
|
numerator = sum([vec1[x] * vec2[x] for x in intersection])
|
||||||
|
|
||||||
|
sum1 = sum([vec1[x] ** 2 for x in list(vec1.keys())])
|
||||||
|
sum2 = sum([vec2[x] ** 2 for x in list(vec2.keys())])
|
||||||
|
denominator = math.sqrt(sum1) * math.sqrt(sum2)
|
||||||
|
|
||||||
|
if not denominator:
|
||||||
|
return 0.0
|
||||||
|
else:
|
||||||
|
return float(numerator) / denominator
|
||||||
|
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def levenshtein(text1, text2):
|
||||||
|
"""
|
||||||
|
Find the similarity between two strings using Levenshtein distance.
|
||||||
|
Returns float score value, 0.0 being completely different strings and 1.0 being equal strings.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
text1: main string to compare against.
|
||||||
|
text2: second string to compare to text1.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
size_x = len(text1) + 1
|
||||||
|
size_y = len(text2) + 1
|
||||||
|
matrix = np.zeros ((size_x, size_y))
|
||||||
|
for x in range(size_x):
|
||||||
|
matrix [x, 0] = x
|
||||||
|
for y in range(size_y):
|
||||||
|
matrix [0, y] = y
|
||||||
|
|
||||||
|
for x in range(1, size_x):
|
||||||
|
for y in range(1, size_y):
|
||||||
|
if text1[x-1] == text2[y-1]:
|
||||||
|
matrix [x,y] = min(
|
||||||
|
matrix[x-1, y] + 1,
|
||||||
|
matrix[x-1, y-1],
|
||||||
|
matrix[x, y-1] + 1
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
matrix [x,y] = min(
|
||||||
|
matrix[x-1,y] + 1,
|
||||||
|
matrix[x-1,y-1] + 1,
|
||||||
|
matrix[x,y-1] + 1
|
||||||
|
)
|
||||||
|
distance = matrix[size_x - 1, size_y - 1]
|
||||||
|
score = (max(len(text1), len(text2)) - distance) / max(len(text1), len(text2))
|
||||||
|
return float(score)
|
||||||
|
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def jaro_winkler(s1, s2):
|
||||||
|
"""
|
||||||
|
Find the similarity between two strings using Jaro-Winkler distance.
|
||||||
|
Returns float score value, 0.0 being completely different strings and 1.0 being equal strings.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
text1: main string to compare against.
|
||||||
|
text2: second string to compare to text1.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if (s1 == s2):
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
len1 = len(s1)
|
||||||
|
len2 = len(s2)
|
||||||
|
max_dist = floor(max(len1, len2) / 2) - 1
|
||||||
|
match = 0
|
||||||
|
hash_s1 = [0] * len(s1)
|
||||||
|
hash_s2 = [0] * len(s2)
|
||||||
|
|
||||||
|
for i in range(len1):
|
||||||
|
for j in range(max(0, i - max_dist),
|
||||||
|
min(len2, i + max_dist + 1)):
|
||||||
|
|
||||||
|
if (s1[i] == s2[j] and hash_s2[j] == 0):
|
||||||
|
hash_s1[i] = 1
|
||||||
|
hash_s2[j] = 1
|
||||||
|
match += 1
|
||||||
|
break
|
||||||
|
|
||||||
|
if (match == 0):
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
t = 0
|
||||||
|
point = 0
|
||||||
|
|
||||||
|
for i in range(len1):
|
||||||
|
if (hash_s1[i]):
|
||||||
|
|
||||||
|
while (hash_s2[point] == 0):
|
||||||
|
point += 1
|
||||||
|
|
||||||
|
if (s1[i] != s2[point]):
|
||||||
|
point += 1
|
||||||
|
t += 1
|
||||||
|
t = t//2
|
||||||
|
|
||||||
|
return float(match/ len1 + match / len2 +
|
||||||
|
(match - t + 1) / match)/ 3.0
|
||||||
|
except:
|
||||||
|
return None
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# encoding: utf-8
|
||||||
|
import heapq
|
||||||
|
from . import algorithims
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def extract(query, choices, match_type='trigram', score_cutoff=0, limit=5):
|
||||||
|
"""
|
||||||
|
Find the similarity between a query item and a list of choices.
|
||||||
|
Returns a tuple of all choices and their associated similarity score.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
query: The string you are wanting to match.
|
||||||
|
choices: An iterable or dictionary-like object containing choices
|
||||||
|
to be matched against the query.
|
||||||
|
score_cutoff: Optional argument for score threshold. If the best
|
||||||
|
match is found, but it is not greater than this number, then
|
||||||
|
return None anyway ("not a good enough match"). Defaults to 0.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if match_type == 'trigram':
|
||||||
|
match_type = algorithims.trigram
|
||||||
|
elif match_type == 'levenshtein':
|
||||||
|
match_type = algorithims.levenshtein
|
||||||
|
elif match_type == 'cosine':
|
||||||
|
match_type = algorithims.cosine
|
||||||
|
elif match_type == 'jaro_winkler':
|
||||||
|
match_type = algorithims.jaro_winkler
|
||||||
|
try:
|
||||||
|
if choices is None or len(choices) == 0:
|
||||||
|
return
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for i in choices:
|
||||||
|
score = (match_type(query, i))
|
||||||
|
data = (i, score)
|
||||||
|
if score >= score_cutoff:
|
||||||
|
results.append(data)
|
||||||
|
|
||||||
|
|
||||||
|
return heapq.nlargest(limit, results, key=lambda i: i[1]) if limit is not None else \
|
||||||
|
sorted(results, key=lambda i: i[1], reverse=True)
|
||||||
|
|
||||||
|
# return results
|
||||||
|
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extractOne(query, choices, match_type='trigram', score_cutoff=0):
|
||||||
|
"""
|
||||||
|
Finds the most similar item to query item from a list of choices.
|
||||||
|
Returns tuple of best choice and its associated similarity score.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
query: The string you are wanting to match.
|
||||||
|
choices: An iterable or dictionary-like object containing choices
|
||||||
|
to be matched against the query.
|
||||||
|
score_cutoff: Optional argument for score threshold. If the best
|
||||||
|
match is found, but it is not greater than this number, then
|
||||||
|
return None anyway ("not a good enough match"). Defaults to 0.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
best_list = extract(query, choices, match_type, score_cutoff)
|
||||||
|
|
||||||
|
best = max(best_list, key=lambda i: i[1])
|
||||||
|
|
||||||
|
return best
|
||||||
|
|
||||||
|
except:
|
||||||
|
return None
|
||||||
+29
-9
@@ -13,7 +13,10 @@ import re
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from platformcode import logger
|
try:
|
||||||
|
from platformcode import logger
|
||||||
|
except ImportError:
|
||||||
|
logger = None
|
||||||
|
|
||||||
HEADERS = {
|
HEADERS = {
|
||||||
'Host': 'translate.google.com',
|
'Host': 'translate.google.com',
|
||||||
@@ -21,9 +24,11 @@ HEADERS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MAX_CONECTION_THREAD = 10
|
MAX_CONECTION_THREAD = 10
|
||||||
|
SL = 'en'
|
||||||
|
TL = 'it'
|
||||||
|
|
||||||
BASE_URL_PROXY = 'https://translate.googleusercontent.com'
|
BASE_URL_PROXY = 'https://translate.googleusercontent.com'
|
||||||
BASE_URL_TRANSLATE = 'https://translate.google.com/translate?hl=it&sl=en&tl=it&u=[TARGET_URL]&sandbox=0' # noqa: E501
|
BASE_URL_TRANSLATE = 'https://translate.google.com/translate?hl=it&sl=' + SL + '&tl=' + TL + '&u=[TARGET_URL]&sandbox=0' # noqa: E501
|
||||||
|
|
||||||
|
|
||||||
def checker_url(html, url):
|
def checker_url(html, url):
|
||||||
@@ -43,7 +48,10 @@ def process_request_proxy(url):
|
|||||||
target_url = \
|
target_url = \
|
||||||
BASE_URL_TRANSLATE.replace('[TARGET_URL]', request.quote(url))
|
BASE_URL_TRANSLATE.replace('[TARGET_URL]', request.quote(url))
|
||||||
|
|
||||||
logger.debug(target_url)
|
if logger:
|
||||||
|
logger.debug(target_url)
|
||||||
|
else:
|
||||||
|
print(target_url)
|
||||||
|
|
||||||
return_html = requests.get(target_url, timeout=20, headers=HEADERS)
|
return_html = requests.get(target_url, timeout=20, headers=HEADERS)
|
||||||
|
|
||||||
@@ -52,10 +60,13 @@ def process_request_proxy(url):
|
|||||||
|
|
||||||
url_request = checker_url(
|
url_request = checker_url(
|
||||||
return_html.text,
|
return_html.text,
|
||||||
BASE_URL_PROXY + '/translate_p?hl=it&sl=en&tl=it&u='
|
BASE_URL_PROXY + '/translate_p?hl=it&sl=' + SL + '&tl=' + TL + '&u='
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(url_request)
|
if logger:
|
||||||
|
logger.debug(url_request)
|
||||||
|
else:
|
||||||
|
print(url_request)
|
||||||
|
|
||||||
request_final = requests.get(
|
request_final = requests.get(
|
||||||
url_request,
|
url_request,
|
||||||
@@ -66,7 +77,10 @@ def process_request_proxy(url):
|
|||||||
url_request_proxy = checker_url(
|
url_request_proxy = checker_url(
|
||||||
request_final.text, 'translate.google')
|
request_final.text, 'translate.google')
|
||||||
|
|
||||||
logger.debug(url_request_proxy)
|
if logger:
|
||||||
|
logger.debug(url_request_proxy)
|
||||||
|
else:
|
||||||
|
print(url_request_proxy)
|
||||||
|
|
||||||
data = None
|
data = None
|
||||||
result = None
|
result = None
|
||||||
@@ -80,12 +94,18 @@ def process_request_proxy(url):
|
|||||||
data = result.content.decode('utf-8', 'ignore')
|
data = result.content.decode('utf-8', 'ignore')
|
||||||
if not PY3:
|
if not PY3:
|
||||||
data = data.encode('utf-8')
|
data = data.encode('utf-8')
|
||||||
logger.debug()
|
if logger:
|
||||||
|
logger.debug()
|
||||||
|
|
||||||
data = re.sub('\s(\w+)=(?!")([^<>\s]+)', r' \1="\2"', data)
|
data = re.sub('\s(\w+)=(?!")([^<>\s]+)', r' \1="\2"', data)
|
||||||
data = re.sub('https://translate\.googleusercontent\.com/.*?u=(.*?)&usg=[A-Za-z0-9_-]+', '\\1', data)
|
data = re.sub('https://translate\.googleusercontent\.com/.*?u=(.*?)&usg=[A-Za-z0-9_-]+', '\\1', data)
|
||||||
data = re.sub('https?://[a-zA-Z0-9]+--' + domain.replace('.', '-') + '\.translate\.goog(/[a-zA-Z0-9#/-]+)', 'https://' + domain + '\\1', data)
|
data = re.sub('https?://[a-zA-Z0-9]+--' + domain.replace('.', '-') + '\.translate\.goog(/[a-zA-Z0-9#/-]+)', 'https://' + domain + '\\1', data)
|
||||||
|
data = re.sub('\s+<', '<', data)
|
||||||
|
data = data.replace('&', '&').replace('https://translate.google.com/website?sl=' + SL + '&tl=' + TL + '&u=', '')
|
||||||
|
|
||||||
return {'url': url.strip(), 'result': result, 'data': data.replace('&', '&')}
|
return {'url': url.strip(), 'result': result, 'data': data}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
if logger:
|
||||||
|
logger.error(e)
|
||||||
|
else:
|
||||||
|
print(e)
|
||||||
|
|||||||
@@ -0,0 +1,601 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# This code is distributed under the terms and conditions
|
||||||
|
# from the Apache License, Version 2.0
|
||||||
|
#
|
||||||
|
# http://opensource.org/licenses/apache2.0.php
|
||||||
|
#
|
||||||
|
# This code was inspired by:
|
||||||
|
# * http://code.activestate.com/recipes/576638-draft-for-an-sqlite3-based-dbm/
|
||||||
|
# * http://code.activestate.com/recipes/526618/
|
||||||
|
|
||||||
|
"""
|
||||||
|
A lightweight wrapper around Python's sqlite3 database, with a dict-like interface
|
||||||
|
and multi-thread access support::
|
||||||
|
|
||||||
|
>>> mydict = SqliteDict('some.db', autocommit=True) # the mapping will be persisted to file `some.db`
|
||||||
|
>>> mydict['some_key'] = any_picklable_object
|
||||||
|
>>> print mydict['some_key']
|
||||||
|
>>> print len(mydict) # etc... all dict functions work
|
||||||
|
|
||||||
|
Pickle is used internally to serialize the values. Keys are strings.
|
||||||
|
|
||||||
|
If you don't use autocommit (default is no autocommit for performance), then
|
||||||
|
don't forget to call `mydict.commit()` when done with a transaction.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
__version__ = '1.7.0.dev0'
|
||||||
|
|
||||||
|
major_version = sys.version_info[0]
|
||||||
|
if major_version < 3: # py <= 2.x
|
||||||
|
if sys.version_info[1] < 5: # py <= 2.4
|
||||||
|
raise ImportError("sqlitedict requires python 2.5 or higher (python 3.3 or higher supported)")
|
||||||
|
|
||||||
|
# necessary to use exec()_ as this would be a SyntaxError in python3.
|
||||||
|
# this is an exact port of six.reraise():
|
||||||
|
def exec_(_code_, _globs_=None, _locs_=None):
|
||||||
|
"""Execute code in a namespace."""
|
||||||
|
if _globs_ is None:
|
||||||
|
frame = sys._getframe(1)
|
||||||
|
_globs_ = frame.f_globals
|
||||||
|
if _locs_ is None:
|
||||||
|
_locs_ = frame.f_locals
|
||||||
|
del frame
|
||||||
|
elif _locs_ is None:
|
||||||
|
_locs_ = _globs_
|
||||||
|
exec("""exec _code_ in _globs_, _locs_""")
|
||||||
|
|
||||||
|
class TimeoutError(OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
exec_("def reraise(tp, value, tb=None):\n"
|
||||||
|
" raise tp, value, tb\n")
|
||||||
|
else:
|
||||||
|
def reraise(tp, value, tb=None):
|
||||||
|
if value is None:
|
||||||
|
value = tp()
|
||||||
|
if value.__traceback__ is not tb:
|
||||||
|
raise value.with_traceback(tb)
|
||||||
|
raise value
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cPickle import dumps, loads, HIGHEST_PROTOCOL as PICKLE_PROTOCOL
|
||||||
|
except ImportError:
|
||||||
|
from pickle import dumps, loads, HIGHEST_PROTOCOL as PICKLE_PROTOCOL
|
||||||
|
|
||||||
|
# some Python 3 vs 2 imports
|
||||||
|
try:
|
||||||
|
from collections import UserDict as DictClass
|
||||||
|
except ImportError:
|
||||||
|
from UserDict import DictMixin as DictClass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from queue import Queue
|
||||||
|
except ImportError:
|
||||||
|
from Queue import Queue
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def open(*args, **kwargs):
|
||||||
|
"""See documentation of the SqliteDict class."""
|
||||||
|
return SqliteDict(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def encode(obj):
|
||||||
|
"""Serialize an object using pickle to a binary format accepted by SQLite."""
|
||||||
|
return sqlite3.Binary(dumps(obj, protocol=PICKLE_PROTOCOL))
|
||||||
|
|
||||||
|
|
||||||
|
def decode(obj):
|
||||||
|
"""Deserialize objects retrieved from SQLite."""
|
||||||
|
return loads(bytes(obj))
|
||||||
|
|
||||||
|
|
||||||
|
class SqliteDict(DictClass):
|
||||||
|
VALID_FLAGS = ['c', 'r', 'w', 'n']
|
||||||
|
|
||||||
|
def __init__(self, filename=None, tablename='unnamed', flag='c',
|
||||||
|
autocommit=False, journal_mode="DELETE", encode=encode, decode=decode, timeout=5):
|
||||||
|
"""
|
||||||
|
Initialize a thread-safe sqlite-backed dictionary. The dictionary will
|
||||||
|
be a table `tablename` in database file `filename`. A single file (=database)
|
||||||
|
may contain multiple tables.
|
||||||
|
|
||||||
|
If no `filename` is given, a random file in temp will be used (and deleted
|
||||||
|
from temp once the dict is closed/deleted).
|
||||||
|
|
||||||
|
If you enable `autocommit`, changes will be committed after each operation
|
||||||
|
(more inefficient but safer). Otherwise, changes are committed on `self.commit()`,
|
||||||
|
`self.clear()` and `self.close()`.
|
||||||
|
|
||||||
|
Set `journal_mode` to 'OFF' if you're experiencing sqlite I/O problems
|
||||||
|
or if you need performance and don't care about crash-consistency.
|
||||||
|
|
||||||
|
The `flag` parameter. Exactly one of:
|
||||||
|
'c': default mode, open for read/write, creating the db/table if necessary.
|
||||||
|
'w': open for r/w, but drop `tablename` contents first (start with empty table)
|
||||||
|
'r': open as read-only
|
||||||
|
'n': create a new database (erasing any existing tables, not just `tablename`!).
|
||||||
|
|
||||||
|
The `encode` and `decode` parameters are used to customize how the values
|
||||||
|
are serialized and deserialized.
|
||||||
|
The `encode` parameter must be a function that takes a single Python
|
||||||
|
object and returns a serialized representation.
|
||||||
|
The `decode` function must be a function that takes the serialized
|
||||||
|
representation produced by `encode` and returns a deserialized Python
|
||||||
|
object.
|
||||||
|
The default is to use pickle.
|
||||||
|
|
||||||
|
The `timeout` defines the maximum time (in seconds) to wait for initial Thread startup.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.in_temp = filename is None
|
||||||
|
if self.in_temp:
|
||||||
|
fd, filename = tempfile.mkstemp(prefix='sqldict')
|
||||||
|
os.close(fd)
|
||||||
|
|
||||||
|
if flag not in SqliteDict.VALID_FLAGS:
|
||||||
|
raise RuntimeError("Unrecognized flag: %s" % flag)
|
||||||
|
self.flag = flag
|
||||||
|
|
||||||
|
if flag == 'n':
|
||||||
|
if os.path.exists(filename):
|
||||||
|
os.remove(filename)
|
||||||
|
|
||||||
|
dirname = os.path.dirname(filename)
|
||||||
|
if dirname:
|
||||||
|
if not os.path.exists(dirname):
|
||||||
|
raise RuntimeError('Error! The directory does not exist, %s' % dirname)
|
||||||
|
|
||||||
|
self.filename = filename
|
||||||
|
|
||||||
|
# Use standard SQL escaping of double quote characters in identifiers, by doubling them.
|
||||||
|
# See https://github.com/RaRe-Technologies/sqlitedict/pull/113
|
||||||
|
self.tablename = tablename.replace('"', '""')
|
||||||
|
|
||||||
|
self.autocommit = autocommit
|
||||||
|
self.journal_mode = journal_mode
|
||||||
|
self.encode = encode
|
||||||
|
self.decode = decode
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
logger.info("opening Sqlite table %r in %r" % (tablename, filename))
|
||||||
|
self.conn = self._new_conn()
|
||||||
|
if self.flag == 'r':
|
||||||
|
if self.tablename not in SqliteDict.get_tablenames(self.filename):
|
||||||
|
msg = 'Refusing to create a new table "%s" in read-only DB mode' % tablename
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
else:
|
||||||
|
MAKE_TABLE = 'CREATE TABLE IF NOT EXISTS "%s" (key TEXT PRIMARY KEY, value BLOB)' % self.tablename
|
||||||
|
self.conn.execute(MAKE_TABLE)
|
||||||
|
self.conn.commit()
|
||||||
|
if flag == 'w':
|
||||||
|
self.clear()
|
||||||
|
|
||||||
|
def _new_conn(self):
|
||||||
|
return SqliteMultithread(self.filename, autocommit=self.autocommit, journal_mode=self.journal_mode,
|
||||||
|
timeout=self.timeout)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
if not hasattr(self, 'conn') or self.conn is None:
|
||||||
|
self.conn = self._new_conn()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *exc_info):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "SqliteDict(%s)" % (self.filename)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self) # no need of something complex
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
# `select count (*)` is super slow in sqlite (does a linear scan!!)
|
||||||
|
# As a result, len() is very slow too once the table size grows beyond trivial.
|
||||||
|
# We could keep the total count of rows ourselves, by means of triggers,
|
||||||
|
# but that seems too complicated and would slow down normal operation
|
||||||
|
# (insert/delete etc).
|
||||||
|
GET_LEN = 'SELECT COUNT(*) FROM "%s"' % self.tablename
|
||||||
|
rows = self.conn.select_one(GET_LEN)[0]
|
||||||
|
return rows if rows is not None else 0
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
# No elements is False, otherwise True
|
||||||
|
GET_MAX = 'SELECT MAX(ROWID) FROM "%s"' % self.tablename
|
||||||
|
m = self.conn.select_one(GET_MAX)[0]
|
||||||
|
# Explicit better than implicit and bla bla
|
||||||
|
return True if m is not None else False
|
||||||
|
|
||||||
|
def iterkeys(self):
|
||||||
|
GET_KEYS = 'SELECT key FROM "%s" ORDER BY rowid' % self.tablename
|
||||||
|
for key in self.conn.select(GET_KEYS):
|
||||||
|
yield key[0]
|
||||||
|
|
||||||
|
def itervalues(self):
|
||||||
|
GET_VALUES = 'SELECT value FROM "%s" ORDER BY rowid' % self.tablename
|
||||||
|
for value in self.conn.select(GET_VALUES):
|
||||||
|
yield self.decode(value[0])
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
GET_ITEMS = 'SELECT key, value FROM "%s" ORDER BY rowid' % self.tablename
|
||||||
|
for key, value in self.conn.select(GET_ITEMS):
|
||||||
|
yield key, self.decode(value)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.iterkeys() if major_version > 2 else list(self.iterkeys())
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return self.itervalues() if major_version > 2 else list(self.itervalues())
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self.iteritems() if major_version > 2 else list(self.iteritems())
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
HAS_ITEM = 'SELECT 1 FROM "%s" WHERE key = ?' % self.tablename
|
||||||
|
return self.conn.select_one(HAS_ITEM, (key,)) is not None
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
GET_ITEM = 'SELECT value FROM "%s" WHERE key = ?' % self.tablename
|
||||||
|
item = self.conn.select_one(GET_ITEM, (key,))
|
||||||
|
if item is None:
|
||||||
|
raise KeyError(key)
|
||||||
|
return self.decode(item[0])
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if self.flag == 'r':
|
||||||
|
raise RuntimeError('Refusing to write to read-only SqliteDict')
|
||||||
|
|
||||||
|
ADD_ITEM = 'REPLACE INTO "%s" (key, value) VALUES (?,?)' % self.tablename
|
||||||
|
self.conn.execute(ADD_ITEM, (key, self.encode(value)))
|
||||||
|
if self.autocommit:
|
||||||
|
self.commit()
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
if self.flag == 'r':
|
||||||
|
raise RuntimeError('Refusing to delete from read-only SqliteDict')
|
||||||
|
|
||||||
|
if key not in self:
|
||||||
|
raise KeyError(key)
|
||||||
|
DEL_ITEM = 'DELETE FROM "%s" WHERE key = ?' % self.tablename
|
||||||
|
self.conn.execute(DEL_ITEM, (key,))
|
||||||
|
if self.autocommit:
|
||||||
|
self.commit()
|
||||||
|
|
||||||
|
def update(self, items=(), **kwds):
|
||||||
|
if self.flag == 'r':
|
||||||
|
raise RuntimeError('Refusing to update read-only SqliteDict')
|
||||||
|
|
||||||
|
try:
|
||||||
|
items = items.items()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
items = [(k, self.encode(v)) for k, v in items]
|
||||||
|
|
||||||
|
UPDATE_ITEMS = 'REPLACE INTO "%s" (key, value) VALUES (?, ?)' % self.tablename
|
||||||
|
self.conn.executemany(UPDATE_ITEMS, items)
|
||||||
|
if kwds:
|
||||||
|
self.update(kwds)
|
||||||
|
if self.autocommit:
|
||||||
|
self.commit()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.iterkeys()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
if self.flag == 'r':
|
||||||
|
raise RuntimeError('Refusing to clear read-only SqliteDict')
|
||||||
|
|
||||||
|
# avoid VACUUM, as it gives "OperationalError: database schema has changed"
|
||||||
|
CLEAR_ALL = 'DELETE FROM "%s";' % self.tablename
|
||||||
|
self.conn.commit()
|
||||||
|
self.conn.execute(CLEAR_ALL)
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_tablenames(filename):
|
||||||
|
"""get the names of the tables in an sqlite db as a list"""
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
raise IOError('file %s does not exist' % (filename))
|
||||||
|
GET_TABLENAMES = 'SELECT name FROM sqlite_master WHERE type="table"'
|
||||||
|
with sqlite3.connect(filename) as conn:
|
||||||
|
cursor = conn.execute(GET_TABLENAMES)
|
||||||
|
res = cursor.fetchall()
|
||||||
|
|
||||||
|
return [name[0] for name in res]
|
||||||
|
|
||||||
|
def commit(self, blocking=True):
|
||||||
|
"""
|
||||||
|
Persist all data to disk.
|
||||||
|
|
||||||
|
When `blocking` is False, the commit command is queued, but the data is
|
||||||
|
not guaranteed persisted (default implication when autocommit=True).
|
||||||
|
"""
|
||||||
|
if self.conn is not None:
|
||||||
|
self.conn.commit(blocking)
|
||||||
|
sync = commit
|
||||||
|
|
||||||
|
def close(self, do_log=True, force=False):
|
||||||
|
if do_log:
|
||||||
|
logger.debug("closing %s" % self)
|
||||||
|
if hasattr(self, 'conn') and self.conn is not None:
|
||||||
|
if self.conn.autocommit and not force:
|
||||||
|
# typically calls to commit are non-blocking when autocommit is
|
||||||
|
# used. However, we need to block on close() to ensure any
|
||||||
|
# awaiting exceptions are handled and that all data is
|
||||||
|
# persisted to disk before returning.
|
||||||
|
self.conn.commit(blocking=True)
|
||||||
|
self.conn.close(force=force)
|
||||||
|
self.conn = None
|
||||||
|
if self.in_temp:
|
||||||
|
try:
|
||||||
|
os.remove(self.filename)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def terminate(self):
|
||||||
|
"""Delete the underlying database file. Use with care."""
|
||||||
|
if self.flag == 'r':
|
||||||
|
raise RuntimeError('Refusing to terminate read-only SqliteDict')
|
||||||
|
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
if self.filename == ':memory:':
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("deleting %s" % self.filename)
|
||||||
|
try:
|
||||||
|
if os.path.isfile(self.filename):
|
||||||
|
os.remove(self.filename)
|
||||||
|
except (OSError, IOError):
|
||||||
|
logger.exception("failed to delete %s" % (self.filename))
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
# like close(), but assume globals are gone by now (do not log!)
|
||||||
|
try:
|
||||||
|
self.close(do_log=False, force=True)
|
||||||
|
except Exception:
|
||||||
|
# prevent error log flood in case of multiple SqliteDicts
|
||||||
|
# closed after connection lost (exceptions are always ignored
|
||||||
|
# in __del__ method.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Adding extra methods for python 2 compatibility (at import time)
|
||||||
|
if major_version == 2:
|
||||||
|
SqliteDict.__nonzero__ = SqliteDict.__bool__
|
||||||
|
del SqliteDict.__bool__ # not needed and confusing
|
||||||
|
|
||||||
|
|
||||||
|
class SqliteMultithread(Thread):
|
||||||
|
"""
|
||||||
|
Wrap sqlite connection in a way that allows concurrent requests from multiple threads.
|
||||||
|
|
||||||
|
This is done by internally queueing the requests and processing them sequentially
|
||||||
|
in a separate thread (in the same order they arrived).
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, filename, autocommit, journal_mode, timeout):
|
||||||
|
super(SqliteMultithread, self).__init__()
|
||||||
|
self.filename = filename
|
||||||
|
self.autocommit = autocommit
|
||||||
|
self.journal_mode = journal_mode
|
||||||
|
# use request queue of unlimited size
|
||||||
|
self.reqs = Queue()
|
||||||
|
self.setDaemon(True) # python2.5-compatible
|
||||||
|
self.exception = None
|
||||||
|
self._sqlitedict_thread_initialized = None
|
||||||
|
self.timeout = timeout
|
||||||
|
self.log = logging.getLogger('sqlitedict.SqliteMultithread')
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
if self.autocommit:
|
||||||
|
conn = sqlite3.connect(self.filename, isolation_level=None, check_same_thread=False)
|
||||||
|
else:
|
||||||
|
conn = sqlite3.connect(self.filename, check_same_thread=False)
|
||||||
|
except Exception:
|
||||||
|
self.log.exception("Failed to initialize connection for filename: %s" % self.filename)
|
||||||
|
self.exception = sys.exc_info()
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn.execute('PRAGMA journal_mode = %s' % self.journal_mode)
|
||||||
|
conn.text_factory = str
|
||||||
|
cursor = conn.cursor()
|
||||||
|
conn.commit()
|
||||||
|
cursor.execute('PRAGMA synchronous=OFF')
|
||||||
|
except Exception:
|
||||||
|
self.log.exception("Failed to execute PRAGMA statements.")
|
||||||
|
self.exception = sys.exc_info()
|
||||||
|
raise
|
||||||
|
|
||||||
|
self._sqlitedict_thread_initialized = True
|
||||||
|
|
||||||
|
res = None
|
||||||
|
while True:
|
||||||
|
req, arg, res, outer_stack = self.reqs.get()
|
||||||
|
if req == '--close--':
|
||||||
|
assert res, ('--close-- without return queue', res)
|
||||||
|
break
|
||||||
|
elif req == '--commit--':
|
||||||
|
conn.commit()
|
||||||
|
if res:
|
||||||
|
res.put('--no more--')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
cursor.execute(req, arg)
|
||||||
|
except Exception:
|
||||||
|
self.exception = (e_type, e_value, e_tb) = sys.exc_info()
|
||||||
|
inner_stack = traceback.extract_stack()
|
||||||
|
|
||||||
|
# An exception occurred in our thread, but we may not
|
||||||
|
# immediately able to throw it in our calling thread, if it has
|
||||||
|
# no return `res` queue: log as level ERROR both the inner and
|
||||||
|
# outer exception immediately.
|
||||||
|
#
|
||||||
|
# Any iteration of res.get() or any next call will detect the
|
||||||
|
# inner exception and re-raise it in the calling Thread; though
|
||||||
|
# it may be confusing to see an exception for an unrelated
|
||||||
|
# statement, an ERROR log statement from the 'sqlitedict.*'
|
||||||
|
# namespace contains the original outer stack location.
|
||||||
|
self.log.error('Inner exception:')
|
||||||
|
for item in traceback.format_list(inner_stack):
|
||||||
|
self.log.error(item)
|
||||||
|
self.log.error('') # deliniate traceback & exception w/blank line
|
||||||
|
for item in traceback.format_exception_only(e_type, e_value):
|
||||||
|
self.log.error(item)
|
||||||
|
|
||||||
|
self.log.error('') # exception & outer stack w/blank line
|
||||||
|
self.log.error('Outer stack:')
|
||||||
|
for item in traceback.format_list(outer_stack):
|
||||||
|
self.log.error(item)
|
||||||
|
self.log.error('Exception will be re-raised at next call.')
|
||||||
|
|
||||||
|
if res:
|
||||||
|
for rec in cursor:
|
||||||
|
res.put(rec)
|
||||||
|
res.put('--no more--')
|
||||||
|
|
||||||
|
if self.autocommit:
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
self.log.debug('received: %s, send: --no more--', req)
|
||||||
|
conn.close()
|
||||||
|
res.put('--no more--')
|
||||||
|
|
||||||
|
def check_raise_error(self):
|
||||||
|
"""
|
||||||
|
Check for and raise exception for any previous sqlite query.
|
||||||
|
|
||||||
|
For the `execute*` family of method calls, such calls are non-blocking and any
|
||||||
|
exception raised in the thread cannot be handled by the calling Thread (usually
|
||||||
|
MainThread). This method is called on `close`, and prior to any subsequent
|
||||||
|
calls to the `execute*` methods to check for and raise an exception in a
|
||||||
|
previous call to the MainThread.
|
||||||
|
"""
|
||||||
|
if self.exception:
|
||||||
|
e_type, e_value, e_tb = self.exception
|
||||||
|
|
||||||
|
# clear self.exception, if the caller decides to handle such
|
||||||
|
# exception, we should not repeatedly re-raise it.
|
||||||
|
self.exception = None
|
||||||
|
|
||||||
|
self.log.error('An exception occurred from a previous statement, view '
|
||||||
|
'the logging namespace "sqlitedict" for outer stack.')
|
||||||
|
|
||||||
|
# The third argument to raise is the traceback object, and it is
|
||||||
|
# substituted instead of the current location as the place where
|
||||||
|
# the exception occurred, this is so that when using debuggers such
|
||||||
|
# as `pdb', or simply evaluating the naturally raised traceback, we
|
||||||
|
# retain the original (inner) location of where the exception
|
||||||
|
# occurred.
|
||||||
|
reraise(e_type, e_value, e_tb)
|
||||||
|
|
||||||
|
def execute(self, req, arg=None, res=None):
|
||||||
|
"""
|
||||||
|
`execute` calls are non-blocking: just queue up the request and return immediately.
|
||||||
|
"""
|
||||||
|
self._wait_for_initialization()
|
||||||
|
self.check_raise_error()
|
||||||
|
|
||||||
|
# NOTE: This might be a lot of information to pump into an input
|
||||||
|
# queue, affecting performance. I've also seen earlier versions of
|
||||||
|
# jython take a severe performance impact for throwing exceptions
|
||||||
|
# so often.
|
||||||
|
stack = traceback.extract_stack()[:-1]
|
||||||
|
self.reqs.put((req, arg or tuple(), res, stack))
|
||||||
|
|
||||||
|
def executemany(self, req, items):
|
||||||
|
for item in items:
|
||||||
|
self.execute(req, item)
|
||||||
|
self.check_raise_error()
|
||||||
|
|
||||||
|
def select(self, req, arg=None):
|
||||||
|
"""
|
||||||
|
Unlike sqlite's native select, this select doesn't handle iteration efficiently.
|
||||||
|
|
||||||
|
The result of `select` starts filling up with values as soon as the
|
||||||
|
request is dequeued, and although you can iterate over the result normally
|
||||||
|
(`for res in self.select(): ...`), the entire result will be in memory.
|
||||||
|
"""
|
||||||
|
res = Queue() # results of the select will appear as items in this queue
|
||||||
|
self.execute(req, arg, res)
|
||||||
|
while True:
|
||||||
|
rec = res.get()
|
||||||
|
self.check_raise_error()
|
||||||
|
if rec == '--no more--':
|
||||||
|
break
|
||||||
|
yield rec
|
||||||
|
|
||||||
|
def select_one(self, req, arg=None):
|
||||||
|
"""Return only the first row of the SELECT, or None if there are no matching rows."""
|
||||||
|
try:
|
||||||
|
return next(iter(self.select(req, arg)))
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def commit(self, blocking=True):
|
||||||
|
if blocking:
|
||||||
|
# by default, we await completion of commit() unless
|
||||||
|
# blocking=False. This ensures any available exceptions for any
|
||||||
|
# previous statement are thrown before returning, and that the
|
||||||
|
# data has actually persisted to disk!
|
||||||
|
self.select_one('--commit--')
|
||||||
|
else:
|
||||||
|
# otherwise, we fire and forget as usual.
|
||||||
|
self.execute('--commit--')
|
||||||
|
|
||||||
|
def close(self, force=False):
|
||||||
|
if force:
|
||||||
|
# If a SqliteDict is being killed or garbage-collected, then select_one()
|
||||||
|
# could hang forever because run() might already have exited and therefore
|
||||||
|
# can't process the request. Instead, push the close command to the requests
|
||||||
|
# queue directly. If run() is still alive, it will exit gracefully. If not,
|
||||||
|
# then there's nothing we can do anyway.
|
||||||
|
self.reqs.put(('--close--', None, Queue(), None))
|
||||||
|
else:
|
||||||
|
# we abuse 'select' to "iter" over a "--close--" statement so that we
|
||||||
|
# can confirm the completion of close before joining the thread and
|
||||||
|
# returning (by semaphore '--no more--'
|
||||||
|
self.select_one('--close--')
|
||||||
|
self.join()
|
||||||
|
|
||||||
|
def _wait_for_initialization(self):
|
||||||
|
"""
|
||||||
|
Polls the 'initialized' flag to be set by the started Thread in run().
|
||||||
|
"""
|
||||||
|
# A race condition may occur without waiting for initialization:
|
||||||
|
# __init__() finishes with the start() call, but the Thread needs some time to actually start working.
|
||||||
|
# If opening the database file fails in run(), an exception will occur and self.exception will be set.
|
||||||
|
# But if we run check_raise_error() before run() had a chance to set self.exception, it will report
|
||||||
|
# a false negative: An exception occured and the thread terminates but self.exception is unset.
|
||||||
|
# This leads to a deadlock while waiting for the results of execute().
|
||||||
|
# By waiting for the Thread to set the initialized flag, we can ensure the thread has successfully
|
||||||
|
# opened the file - and possibly set self.exception to be detected by check_raise_error().
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
while time.time() - start_time < self.timeout:
|
||||||
|
if self._sqlitedict_thread_initialized or self.exception:
|
||||||
|
return
|
||||||
|
time.sleep(0.1)
|
||||||
|
raise TimeoutError("SqliteMultithread failed to flag initialization withing %0.0f seconds." % self.timeout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(__version__)
|
||||||
+151
-195
@@ -5,23 +5,22 @@
|
|||||||
|
|
||||||
|
|
||||||
import xbmc, xbmcgui, re, base64, inspect, sys
|
import xbmc, xbmcgui, re, base64, inspect, sys
|
||||||
from core import jsontools, tvdb, scrapertools, filetools
|
from core import jsontools, tmdb, scrapertools, filetools
|
||||||
from core.item import Item
|
from core.item import Item
|
||||||
from core.support import typo, match, dbg, Item
|
from core.support import typo, match, dbg, Item
|
||||||
from platformcode import config, platformtools, logger
|
from platformcode import config, platformtools, logger
|
||||||
PY3 = True if sys.version_info[0] >= 3 else False
|
PY3 = True if sys.version_info[0] >= 3 else False
|
||||||
|
|
||||||
# Json Var
|
# Json Var
|
||||||
TVSHOW_RENUMERATE = "TVSHOW_AUTORENUMBER"
|
RENUMBER = 'TVSHOW_AUTORENUMBER'
|
||||||
ID = "ID"
|
ID = 'id'
|
||||||
SEASON = "Season"
|
SEASONSDICT = 'seasons'
|
||||||
EPISODE = "Episode"
|
SEASON = 'season'
|
||||||
SPECIAL = "Special"
|
EPISODE = 'episode'
|
||||||
MODE = "Mode"
|
EPISODES = 'episodes'
|
||||||
EPLIST = "EpList"
|
SPECIALEPISODES = 'specials'
|
||||||
CHECK = "ReCheck"
|
MANUALMODE = 'manual'
|
||||||
SPLIST = "SpList"
|
GROUP = 'info'
|
||||||
TYPE = "Type"
|
|
||||||
|
|
||||||
# helper Functions
|
# helper Functions
|
||||||
def check(item):
|
def check(item):
|
||||||
@@ -41,21 +40,15 @@ def filename(item):
|
|||||||
|
|
||||||
def load(item):
|
def load(item):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
try:
|
try: json = jsontools.load(open(filename(item), "r").read())[RENUMBER]
|
||||||
json_file = open(filename(item), "r").read()
|
except: json = {}
|
||||||
json = jsontools.load(json_file)[TVSHOW_RENUMERATE]
|
|
||||||
|
|
||||||
except:
|
|
||||||
json = {}
|
|
||||||
|
|
||||||
return json
|
return json
|
||||||
|
|
||||||
|
|
||||||
def write(item, json):
|
def write(item, json):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
json_file = open(filename(item), "r").read()
|
js = jsontools.load(open(filename(item), "r").read())
|
||||||
js = jsontools.load(json_file)
|
js[RENUMBER] = json
|
||||||
js[TVSHOW_RENUMERATE] = json
|
|
||||||
with open(filename(item), "w") as file:
|
with open(filename(item), "w") as file:
|
||||||
file.write(jsontools.dump(js))
|
file.write(jsontools.dump(js))
|
||||||
file.close()
|
file.close()
|
||||||
@@ -69,15 +62,6 @@ def b64(json, mode = 'encode'):
|
|||||||
ret = jsontools.load(base64.b64decode(json))
|
ret = jsontools.load(base64.b64decode(json))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def RepresentsInt(s):
|
|
||||||
# Controllo Numro Stagione
|
|
||||||
logger.debug()
|
|
||||||
try:
|
|
||||||
int(s)
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def find_episodes(item):
|
def find_episodes(item):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
ch = __import__('channels.' + item.channel, fromlist=["channels.%s" % item.channel])
|
ch = __import__('channels.' + item.channel, fromlist=["channels.%s" % item.channel])
|
||||||
@@ -88,6 +72,15 @@ def busy(state):
|
|||||||
if state: xbmc.executebuiltin('ActivateWindow(busydialognocancel)')
|
if state: xbmc.executebuiltin('ActivateWindow(busydialognocancel)')
|
||||||
else: xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
|
else: xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
|
||||||
|
|
||||||
|
def RepresentsInt(s):
|
||||||
|
# Controllo Numro Stagione
|
||||||
|
logger.debug()
|
||||||
|
try:
|
||||||
|
int(s)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
def start(itemlist, item=None):
|
def start(itemlist, item=None):
|
||||||
if not itemlist: return
|
if not itemlist: return
|
||||||
@@ -106,48 +99,44 @@ class autorenumber():
|
|||||||
def __init__(self, itemlist, item=None):
|
def __init__(self, itemlist, item=None):
|
||||||
self.item = item
|
self.item = item
|
||||||
self.itemlist = itemlist
|
self.itemlist = itemlist
|
||||||
|
self.renumberdict = load(self.itemlist[0]) if self.itemlist else load(item) if item else {}
|
||||||
|
self.selectspecials = False
|
||||||
|
self.manual = False
|
||||||
self.auto = False
|
self.auto = False
|
||||||
self.dictSeries = load(self.itemlist[0]) if self.itemlist else load(item) if item else {}
|
|
||||||
self.Episodes = {}
|
|
||||||
self.sp = False
|
|
||||||
if self.item:
|
if self.item:
|
||||||
self.auto = config.get_setting('autorenumber', item.channel)
|
self.auto = config.get_setting('autorenumber', item.channel)
|
||||||
self.title = self.item.fulltitle.strip()
|
self.title = self.item.fulltitle.strip()
|
||||||
if match(self.itemlist[0].title, patron=r'[Ss]?(\d+)(?:x|_|\s+)[Ee]?[Pp]?(\d+)').match:
|
if match(self.itemlist[0].title, patron=r'[Ss]?(\d+)(?:x|_|\s+)[Ee]?[Pp]?(\d+)').match:
|
||||||
item.exit = True
|
item.exit = True
|
||||||
return
|
return
|
||||||
elif self.item.channel in self.item.channel_prefs and TVSHOW_RENUMERATE in self.item.channel_prefs[item.channel] and self.title not in self.dictSeries:
|
elif self.item.channel in self.item.channel_prefs and RENUMBER in self.item.channel_prefs[item.channel] and self.title not in self.renumberdict:
|
||||||
from core.videolibrarytools import check_renumber_options
|
from core.videolibrarytools import check_renumber_options
|
||||||
from specials.videolibrary import update_videolibrary
|
from specials.videolibrary import update_videolibrary
|
||||||
check_renumber_options(self.item)
|
check_renumber_options(self.item)
|
||||||
update_videolibrary(self.item)
|
update_videolibrary(self.item)
|
||||||
if self.title in self.dictSeries and ID in self.dictSeries[self.title] and self.dictSeries[self.title][ID] != '0':
|
self.series = self.renumberdict.get(self.title,{})
|
||||||
self.id = self.dictSeries[self.title][ID]
|
self.id = self.series.get(ID, 0)
|
||||||
self.Episodes = b64(self.dictSeries[self.title][EPISODE], 'decode') if EPISODE in self.dictSeries[self.title] else {}
|
self.episodes = self.series.get(EPISODES,{})
|
||||||
self.Season = self.dictSeries[self.title][SEASON]
|
self.seasonsdict = self.series.get(SEASONSDICT,{})
|
||||||
self.Mode = self.dictSeries[self.title].get(MODE, False)
|
self.season = self.series.get(SEASON, -1)
|
||||||
self.Type = self.dictSeries[self.title].get(TYPE, False)
|
self.episode = self.series.get(EPISODE, -1)
|
||||||
if self.item.renumber:
|
self.manual = self.series.get(MANUALMODE, False)
|
||||||
self.config()
|
self.specials = self.series.get(SPECIALEPISODES, {})
|
||||||
else:
|
if self.id and self.episodes and self.season >= 0 and self.episode >= 0:
|
||||||
self.renumber()
|
if self.item.renumber: self.config()
|
||||||
|
else:self.renumber()
|
||||||
elif self.auto or self.item.renumber:
|
elif self.auto or self.item.renumber:
|
||||||
self.Episodes = {}
|
self.episodes = {}
|
||||||
self.config()
|
self.config()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for item in self.itemlist:
|
for item in self.itemlist:
|
||||||
item.context = [{"title": typo(config.get_localized_string(70585), 'bold'),
|
item.context = [{"title": typo(config.get_localized_string(70585), 'bold'),
|
||||||
"action": "start",
|
"action": "start",
|
||||||
"channel": "autorenumber",
|
"channel": "autorenumber",
|
||||||
"from_channel": item.channel,
|
"from_channel": item.channel,
|
||||||
"from_action": item.action}]
|
"from_action": item.action}]
|
||||||
|
|
||||||
def config(self):
|
def config(self):
|
||||||
self.id = ''
|
|
||||||
if self.title in self.dictSeries:
|
|
||||||
self.id = self.dictSeries[self.title].get(ID,'')
|
|
||||||
|
|
||||||
# Pulizia del Titolo
|
# Pulizia del Titolo
|
||||||
if any( word in self.title.lower() for word in ['specials', 'speciali']):
|
if any( word in self.title.lower() for word in ['specials', 'speciali']):
|
||||||
self.title = re.sub(r'\s*specials|\s*speciali', '', self.title.lower())
|
self.title = re.sub(r'\s*specials|\s*speciali', '', self.title.lower())
|
||||||
@@ -155,184 +144,149 @@ class autorenumber():
|
|||||||
self.item.contentSerieName = self.title.rstrip('123456789 ')
|
self.item.contentSerieName = self.title.rstrip('123456789 ')
|
||||||
|
|
||||||
while not self.item.exit:
|
while not self.item.exit:
|
||||||
tvdb.find_and_set_infoLabels(self.item)
|
self.item.infoLabels['tmdb_id'] = ''
|
||||||
if self.item.infoLabels['tvdb_id']: self.item.exit = True
|
self.item.infoLabels['year'] = '-'
|
||||||
else: self.item = platformtools.dialog_info(self.item, 'tvdb')
|
self.item.contentType ='tvshow'
|
||||||
|
tmdb.find_and_set_infoLabels(self.item)
|
||||||
|
if self.item.infoLabels['tmdb_id']: self.item.exit = True
|
||||||
|
else: self.item = platformtools.dialog_info(self.item, 'tmdb')
|
||||||
|
|
||||||
# Rinumerazione Automatica
|
# Rinumerazione Automatica
|
||||||
if (not self.id and self.auto) or self.item.renumber:
|
if (not self.id and self.auto) or self.item.renumber:
|
||||||
self.id = self.item.infoLabels['tvdb_id'] if 'tvdb_id' in self.item.infoLabels else ''
|
self.id = self.item.infoLabels['tmdb_id'] if 'tmdb_id' in self.item.infoLabels else 0
|
||||||
if self.id:
|
if self.id:
|
||||||
self.dictRenumber = {ID: self.id}
|
self.series = {ID: self.id}
|
||||||
self.dictSeries[self.title] = self.dictRenumber
|
self.renumberdict[self.title] = self.series
|
||||||
if any(word in self.title.lower() for word in ['specials', 'speciali']): season = '0'
|
if any(word in self.title.lower() for word in ['specials', 'speciali']): season = 0
|
||||||
elif RepresentsInt(self.title.split()[-1]): season = self.title.split()[-1]
|
elif RepresentsInt(self.title.split()[-1]): season = int(self.title.split()[-1])
|
||||||
else: season = '1'
|
else: season = 1
|
||||||
self.Season = self.dictRenumber[SEASON] = season
|
self.season = self.series[SEASON] = season
|
||||||
|
self.episode = 1
|
||||||
self.renumber()
|
self.renumber()
|
||||||
|
|
||||||
|
|
||||||
def renumber(self):
|
def renumber(self):
|
||||||
if not self.item.renumber and self.itemlist:
|
if not self.item.renumber and self.itemlist:
|
||||||
if '|' in self.Season:
|
|
||||||
season = int(self.Season.split('|')[0])
|
|
||||||
addNumber = int(self.Season.split('|')[-1]) - 1
|
|
||||||
else:
|
|
||||||
season = int(self.Season)
|
|
||||||
addNumber = 0
|
|
||||||
for item in self.itemlist:
|
for item in self.itemlist:
|
||||||
if not match(item.title, patron=r'[Ss]?(\d+)(?:x|_|\s+)[Ee]?[Pp]?(\d+)').match:
|
if not match(item.title, patron=r'[Ss]?(\d+)(?:x|_|\s+)[Ee]?[Pp]?(\d+)').match:
|
||||||
number = match(item.title, patron=r'(\d+)').match.lstrip('0')
|
number = match(item.title, patron=r'(\d+)').match.lstrip('0')
|
||||||
if number:
|
if number:
|
||||||
if number in self.Episodes:
|
if not number in self.episodes: self.makelist()
|
||||||
if season > 0: item.title = typo(self.Episodes[number] + ' - ', 'bold') + item.title
|
item.title = '{} - {}'.format(typo(self.episodes[number], 'bold'), item.title)
|
||||||
else: item.title = typo('0x%s - ' % str(int(number) + addNumber), 'bold') + item.title
|
|
||||||
else:
|
|
||||||
self.makelist()
|
|
||||||
if season > 0: item.title = typo(self.Episodes[number] + ' - ', 'bold') + item.title
|
|
||||||
else: item.title = typo('0x%s - ' % str(int(number) + addNumber), 'bold') + item.title
|
|
||||||
else:
|
else:
|
||||||
self.makelist()
|
self.makelist()
|
||||||
|
|
||||||
|
|
||||||
def makelist(self):
|
def makelist(self):
|
||||||
FirstOfSeason= 0
|
self.epdict = {}
|
||||||
self.EpList = b64(self.dictSeries[self.title][EPLIST], 'decode') if EPLIST in self.dictSeries[self.title] else []
|
self.group = self.renumberdict[self.title].get(GROUP, None)
|
||||||
self.Pages = self.dictSeries[self.title].get(CHECK, [1])
|
|
||||||
self.Mode = self.dictSeries[self.title].get(MODE, False)
|
|
||||||
self.Type = self.dictSeries[self.title].get(TYPE, False)
|
|
||||||
Specials = {}
|
|
||||||
Seasons = {}
|
|
||||||
|
|
||||||
if '|' in self.Season:
|
|
||||||
ep = int(self.Season.split('|')[-1])
|
|
||||||
Season = int(self.Season.split('|')[0])
|
|
||||||
else:
|
|
||||||
Season = int(self.Season)
|
|
||||||
ep = 1
|
|
||||||
|
|
||||||
busy(True)
|
busy(True)
|
||||||
itemlist = find_episodes(self.item)
|
itemlist = find_episodes(self.item)
|
||||||
busy(False)
|
busy(False)
|
||||||
|
|
||||||
if self.item.renumber:
|
if self.item.renumber or self.manual:
|
||||||
self.s = Season
|
self.item.renumber = False
|
||||||
self.e = 1
|
self.season, self.episode, self.manual, self.specials, Manual, Exit = SelectreNumeration(self, itemlist)
|
||||||
Season, Episode, self.Mode, Specials, Seasons, Exit = SelectreNumeration(self, itemlist)
|
if Exit:
|
||||||
if Exit: return
|
self.item.exit = True
|
||||||
if ep != 1: self.Season = '%s|%s' % (Season, Episode)
|
return
|
||||||
else: self.Season = str(Season)
|
if self.manual:
|
||||||
|
self.episodes = Manual
|
||||||
elif self.Episodes and not self.Mode:
|
|
||||||
self.s = Season
|
|
||||||
self.e = ep
|
|
||||||
self.sp = True
|
|
||||||
Season, Episode, self.Mode, Specials, Seasons, Exit = SelectreNumeration(self, itemlist)
|
|
||||||
|
|
||||||
if self.Mode:
|
|
||||||
if not Seasons:
|
|
||||||
self.s = 1
|
|
||||||
self.e = 1
|
|
||||||
Season, Episode, self.Mode, Specials, Seasons, Exit = SelectreNumeration(self, itemlist, True)
|
|
||||||
self.Episodes = Seasons
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Ricava Informazioni da TVDB
|
if self.group:
|
||||||
checkpages = []
|
Id = self.group.split('/')[-1]
|
||||||
exist = True
|
else:
|
||||||
Page = self.Pages[-1]
|
Id = None
|
||||||
Episode = ep
|
groups = tmdb.get_groups(self.item)
|
||||||
|
if groups:
|
||||||
|
Id = tmdb.select_group(groups)
|
||||||
|
|
||||||
|
if Id:
|
||||||
|
self.group = 'https://www.themoviedb.org/tv/{}/episode_group/{}'.format(self.item.infoLabels['tmdb_id'], Id)
|
||||||
|
seasons = []
|
||||||
|
groupedSeasons = tmdb.get_group(Id)
|
||||||
|
for groupedSeason in groupedSeasons:
|
||||||
|
seasons.append({'season_number':groupedSeason['order'], 'episode_count':len(groupedSeason['episodes'])})
|
||||||
|
else:
|
||||||
|
seasons = tmdb.Tmdb(id_Tmdb=self.id).get_list_episodes()
|
||||||
|
count = 0
|
||||||
|
for season in seasons:
|
||||||
|
s = season['season_number']
|
||||||
|
c = season['episode_count']
|
||||||
|
self.seasonsdict[str(s)] = c
|
||||||
|
if s > 0:
|
||||||
|
for e in range(1, c + 1):
|
||||||
|
count += 1
|
||||||
|
self.epdict[count] = '{}x{:02d}'.format(s,e)
|
||||||
|
|
||||||
|
firstep = 0
|
||||||
|
if self.season > 1:
|
||||||
|
for c in range(1, self.season):
|
||||||
|
firstep += self.seasonsdict[str(c)]
|
||||||
|
firstep += self.episode - 1
|
||||||
|
count = 0
|
||||||
|
if self.epdict:
|
||||||
|
for item in itemlist:
|
||||||
|
if not match(re.sub(r'\[[^\]]+\]','',item.title), patron=r'[Ss]?(\d+)(?:x|_|\s+)[Ee]?[Pp]?(\d+)').match:
|
||||||
|
# Otiene Numerazione Episodi
|
||||||
|
scraped_ep = match(re.sub(r'\[[^\]]+\]','',item.title), patron=r'(\d+)').match
|
||||||
|
if scraped_ep:
|
||||||
|
episode = int(scraped_ep)
|
||||||
|
if episode == 0:
|
||||||
|
self.episodes[str(episode)] = '0x01'
|
||||||
|
elif str(episode) in self.specials:
|
||||||
|
self.episodes[str(episode)] = self.specials[str(episode)]
|
||||||
|
count += 1
|
||||||
|
elif episode - count + firstep in self.epdict:
|
||||||
|
self.episodes[str(episode)] = self.epdict[episode - count + firstep]
|
||||||
|
else:
|
||||||
|
self.episodes[str(episode)] = '0x{:02d}'.format(count + 1)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if self.episodes: self.renumberdict[self.title][EPISODES] = self.episodes
|
||||||
|
if self.group: self.renumberdict[self.title][GROUP] = self.group
|
||||||
|
self.renumberdict[self.title][MANUALMODE] = self.manual
|
||||||
|
self.renumberdict[self.title][SEASON] = self.season
|
||||||
|
self.renumberdict[self.title][EPISODE] = self.episode
|
||||||
|
self.renumberdict[self.title][SPECIALEPISODES] = self.specials
|
||||||
|
self.renumberdict[self.title][SEASONSDICT] = self.seasonsdict
|
||||||
|
write(self.item, self.renumberdict)
|
||||||
|
# if self.auto: self.renumber()
|
||||||
|
|
||||||
while exist:
|
|
||||||
data = tvdb.Tvdb(tvdb_id=self.id).get_list_episodes(self.id, Page)
|
|
||||||
if data:
|
|
||||||
for episode in data['data']:
|
|
||||||
if episode['firstAired'] and [episode['firstAired'], episode['airedSeason'], episode['airedEpisodeNumber']] not in self.EpList:
|
|
||||||
self.EpList.append([episode['firstAired'], episode['airedSeason'], episode['airedEpisodeNumber']])
|
|
||||||
Page += 1
|
|
||||||
else:
|
|
||||||
if Page not in checkpages:
|
|
||||||
checkpages.append(Page -1)
|
|
||||||
exist = False
|
|
||||||
self.Pages = [checkpages[-1]]
|
|
||||||
self.EpList.sort()
|
|
||||||
|
|
||||||
# Crea Dizionari per la Rinumerazione
|
|
||||||
if self.EpList:
|
|
||||||
self.specials = []
|
|
||||||
self.regular = {}
|
|
||||||
self.complete = {}
|
|
||||||
allep = 1
|
|
||||||
specialep = 0
|
|
||||||
|
|
||||||
for episode in self.EpList:
|
|
||||||
self.complete[allep] = [str(episode[1]) + 'x' + str(episode[2]), episode[0]]
|
|
||||||
if episode[1] == 0:
|
|
||||||
self.specials.append(allep)
|
|
||||||
specialep = specialep + 1
|
|
||||||
else:
|
|
||||||
self.regular[ep] = [str(episode[1]) + 'x' + str(episode[2]), str(episode[0]), allep - 1]
|
|
||||||
ep = ep + 1
|
|
||||||
allep = allep + 1
|
|
||||||
|
|
||||||
if Season > 1:
|
|
||||||
for numbers, data in self.regular.items():
|
|
||||||
if data[0] == str(Season) + 'x1':
|
|
||||||
FirstOfSeason = numbers - 1
|
|
||||||
else: FirstOfSeason = Episode - 1
|
|
||||||
|
|
||||||
addiction = 0
|
|
||||||
for item in itemlist:
|
|
||||||
if not match(re.sub(r'\[[^\]]+\]','',item.title), patron=r'[Ss]?(\d+)(?:x|_|\s+)[Ee]?[Pp]?(\d+)').match:
|
|
||||||
# Otiene Numerazione Episodi
|
|
||||||
scraped_ep = match(re.sub(r'\[[^\]]+\]','',item.title), patron=r'(\d+)').match
|
|
||||||
if scraped_ep:
|
|
||||||
episode = int(scraped_ep)
|
|
||||||
number = episode + FirstOfSeason - addiction
|
|
||||||
if episode == 0:
|
|
||||||
self.Episodes[str(episode)] = str(self.complete[self.regular[FirstOfSeason+1][2]][0])
|
|
||||||
elif episode in Specials:
|
|
||||||
self.Episodes[str(episode)] = Specials[episode]
|
|
||||||
addiction += 1
|
|
||||||
elif number <= len(self.regular) and number in self.regular:
|
|
||||||
self.Episodes[str(episode)] = str(self.regular[number][0])
|
|
||||||
else:
|
|
||||||
try: self.Episodes[str(episode)] = str(self.complete[self.regular[number+2][2]][0])
|
|
||||||
except: self.Episodes[str(episode)] = '0x0'
|
|
||||||
|
|
||||||
if self.Episodes: self.dictSeries[self.title][EPISODE] = b64(jsontools.dump(self.Episodes))
|
|
||||||
self.dictSeries[self.title][EPLIST] = b64(jsontools.dump(self.EpList))
|
|
||||||
self.dictSeries[self.title][MODE] = self.Mode
|
|
||||||
self.dictSeries[self.title][SEASON] = self.Season
|
|
||||||
self.dictSeries[self.title][CHECK] = self.Pages
|
|
||||||
write(self.item, self.dictSeries)
|
|
||||||
|
|
||||||
if self.auto: self.renumber()
|
|
||||||
|
|
||||||
|
|
||||||
def SelectreNumeration(opt, itemlist, manual=False):
|
def SelectreNumeration(opt, itemlist, manual=False):
|
||||||
class SelectreNumerationWindow(xbmcgui.WindowXMLDialog):
|
class SelectreNumerationWindow(xbmcgui.WindowXMLDialog):
|
||||||
def start(self, opt):
|
def start(self, opt):
|
||||||
self.episodes = opt.Episodes if opt.Episodes else {}
|
self.episodes = opt.episodes if opt.episodes else {}
|
||||||
self.dictSeries = opt.dictSeries
|
self.renumberdict = opt.renumberdict
|
||||||
self.item = opt.item
|
self.item = opt.item
|
||||||
self.title = opt.title
|
self.title = opt.title
|
||||||
self.season = opt.s
|
self.season = opt.season
|
||||||
self.episode = opt.e
|
self.episode = opt.episode
|
||||||
self.mode = opt.Mode
|
self.manual = opt.manual
|
||||||
self.sp = opt.sp
|
self.sp = opt.selectspecials
|
||||||
self.manual = opt.manual
|
self.manual = opt.manual
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
self.Exit = False
|
self.Exit = False
|
||||||
|
|
||||||
self.itemlist = opt.itemlist
|
self.itemlist = opt.itemlist
|
||||||
self.count = 1
|
self.count = 1
|
||||||
self.specials = {}
|
self.specials = opt.specials
|
||||||
self.items = []
|
self.items = []
|
||||||
self.selected = []
|
self.selected = []
|
||||||
self.seasons = {}
|
self.seasons = {}
|
||||||
|
self.seasonsdict = opt.seasonsdict
|
||||||
|
|
||||||
self.doModal()
|
self.doModal()
|
||||||
return self.season, self.episode, self.mode, self.specials, self.seasons, self.Exit
|
return self.season, self.episode, self.manual, self.specials, self.seasons, self.Exit
|
||||||
|
|
||||||
def onInit(self):
|
def onInit(self):
|
||||||
# Compatibility with Kodi 18
|
# Compatibility with Kodi 18
|
||||||
@@ -349,7 +303,7 @@ def SelectreNumeration(opt, itemlist, manual=False):
|
|||||||
if fanart: self.getControl(MBACKGROUND).setImage(fanart)
|
if fanart: self.getControl(MBACKGROUND).setImage(fanart)
|
||||||
self.getControl(INFO).setLabel(typo(config.get_localized_string(70822) + self.title, 'bold'))
|
self.getControl(INFO).setLabel(typo(config.get_localized_string(70822) + self.title, 'bold'))
|
||||||
|
|
||||||
self.mode = True
|
self.manual = True
|
||||||
|
|
||||||
se = '1'
|
se = '1'
|
||||||
ep = '1'
|
ep = '1'
|
||||||
@@ -434,17 +388,19 @@ def SelectreNumeration(opt, itemlist, manual=False):
|
|||||||
self.setFocusId(focus - 1)
|
self.setFocusId(focus - 1)
|
||||||
elif action in [UP]:
|
elif action in [UP]:
|
||||||
if focus in [S]:
|
if focus in [S]:
|
||||||
s += 1
|
if str(s + 1) in self.seasonsdict:
|
||||||
self.getControl(S).setLabel(str(s))
|
s += 1
|
||||||
|
self.getControl(S).setLabel(str(s))
|
||||||
elif focus in [E]:
|
elif focus in [E]:
|
||||||
e += 1
|
if self.seasonsdict[str(s)] > e:
|
||||||
self.getControl(E).setLabel(str(e))
|
e += 1
|
||||||
|
self.getControl(E).setLabel(str(e))
|
||||||
elif action in [DOWN]:
|
elif action in [DOWN]:
|
||||||
if focus in [S]:
|
if focus in [S]:
|
||||||
if s > 0: s -= 1
|
if str(s - 1) in self.seasonsdict: s -= 1
|
||||||
self.getControl(S).setLabel(str(s))
|
self.getControl(S).setLabel(str(s))
|
||||||
elif focus in [E]:
|
elif focus in [E]:
|
||||||
if e > 0: e -= 1
|
if e > 1: e -= 1
|
||||||
self.getControl(E).setLabel(str(e))
|
self.getControl(E).setLabel(str(e))
|
||||||
# MANUAL
|
# MANUAL
|
||||||
if focus in [MS, ME]:
|
if focus in [MS, ME]:
|
||||||
@@ -508,7 +464,7 @@ def SelectreNumeration(opt, itemlist, manual=False):
|
|||||||
# OPEN MANUAL
|
# OPEN MANUAL
|
||||||
elif control_id in [M]:
|
elif control_id in [M]:
|
||||||
self.getControl(INFO).setLabel(typo(config.get_localized_string(70823) + self.title, 'bold'))
|
self.getControl(INFO).setLabel(typo(config.get_localized_string(70823) + self.title, 'bold'))
|
||||||
self.mode = True
|
self.manual = True
|
||||||
if self.episodes:
|
if self.episodes:
|
||||||
items = []
|
items = []
|
||||||
se = '1'
|
se = '1'
|
||||||
@@ -539,8 +495,8 @@ def SelectreNumeration(opt, itemlist, manual=False):
|
|||||||
# DELETE
|
# DELETE
|
||||||
if control_id in [D]:
|
if control_id in [D]:
|
||||||
self.Exit = True
|
self.Exit = True
|
||||||
self.dictSeries.pop(self.title)
|
self.renumberdict.pop(self.title)
|
||||||
write(self.item, self.dictSeries)
|
write(self.item, self.renumberdict)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
## SPECIAL SECTION
|
## SPECIAL SECTION
|
||||||
@@ -548,7 +504,7 @@ def SelectreNumeration(opt, itemlist, manual=False):
|
|||||||
p1 = self.getControl(SELECTED).getSelectedPosition()
|
p1 = self.getControl(SELECTED).getSelectedPosition()
|
||||||
if control_id in [LIST]:
|
if control_id in [LIST]:
|
||||||
item = self.getControl(LIST).getSelectedItem()
|
item = self.getControl(LIST).getSelectedItem()
|
||||||
it = xbmcgui.ListItem(str(len(self.selected) + 1))
|
it = xbmcgui.ListItem(str(len(self.selected) + len(self.specials) + 1))
|
||||||
it.setProperty('title', item.getLabel())
|
it.setProperty('title', item.getLabel())
|
||||||
self.selected.append(it)
|
self.selected.append(it)
|
||||||
index = self.getControl(SELECTED).getSelectedPosition()
|
index = self.getControl(SELECTED).getSelectedPosition()
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ def run(item=None):
|
|||||||
logger.debug()
|
logger.debug()
|
||||||
if not item:
|
if not item:
|
||||||
# Extract item from sys.argv
|
# Extract item from sys.argv
|
||||||
if sys.argv[2]:
|
if sys.argv[2] and 'play' in sys.argv[2]:
|
||||||
|
sp = sys.argv[2].split('/')
|
||||||
|
if len(sp) == 3:
|
||||||
|
item = Item(channel=sp[1], livefilter=sp[2], action='play')
|
||||||
|
# If no item, this is mainlist
|
||||||
|
elif sys.argv[2]:
|
||||||
sp = sys.argv[2].split('&')
|
sp = sys.argv[2].split('&')
|
||||||
url = sp[0]
|
url = sp[0]
|
||||||
item = Item().fromurl(url)
|
item = Item().fromurl(url)
|
||||||
@@ -49,7 +54,7 @@ def run(item=None):
|
|||||||
for e in sp[1:]:
|
for e in sp[1:]:
|
||||||
key, val = e.split('=')
|
key, val = e.split('=')
|
||||||
item.__setattr__(key, val)
|
item.__setattr__(key, val)
|
||||||
# If no item, this is mainlist
|
|
||||||
else:
|
else:
|
||||||
item = Item(channel="channelselector", action="getmainlist", viewmode="movie")
|
item = Item(channel="channelselector", action="getmainlist", viewmode="movie")
|
||||||
if not config.get_setting('show_once'):
|
if not config.get_setting('show_once'):
|
||||||
@@ -337,6 +342,10 @@ def run(item=None):
|
|||||||
else:
|
else:
|
||||||
if platformtools.dialog_yesno(config.get_localized_string(60038), config.get_localized_string(60015)):
|
if platformtools.dialog_yesno(config.get_localized_string(60038), config.get_localized_string(60015)):
|
||||||
run(Item(channel="setting", action="report_menu"))
|
run(Item(channel="setting", action="report_menu"))
|
||||||
|
finally:
|
||||||
|
if not item.action.startswith('play'):
|
||||||
|
from core import db
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def new_search(item, channel=None):
|
def new_search(item, channel=None):
|
||||||
@@ -469,6 +478,7 @@ def play_from_library(item):
|
|||||||
else:
|
else:
|
||||||
# Pop-up window
|
# Pop-up window
|
||||||
from specials import videolibrary
|
from specials import videolibrary
|
||||||
|
from core.channeltools import get_channel_parameters
|
||||||
p_dialog = platformtools.dialog_progress_bg(config.get_localized_string(20000), config.get_localized_string(60683))
|
p_dialog = platformtools.dialog_progress_bg(config.get_localized_string(20000), config.get_localized_string(60683))
|
||||||
p_dialog.update(0, '')
|
p_dialog.update(0, '')
|
||||||
item.play_from = 'window'
|
item.play_from = 'window'
|
||||||
@@ -499,9 +509,10 @@ def play_from_library(item):
|
|||||||
quality = '[B][' + item.quality + '][/B]' if item.quality else ''
|
quality = '[B][' + item.quality + '][/B]' if item.quality else ''
|
||||||
if item.server:
|
if item.server:
|
||||||
path = filetools.join(config.get_runtime_path(), 'servers', item.server.lower() + '.json')
|
path = filetools.join(config.get_runtime_path(), 'servers', item.server.lower() + '.json')
|
||||||
name = jsontools.load(open(path, "r").read())['name']
|
name = jsontools.load(open(path, "rb").read())['name']
|
||||||
if name.startswith('@'): name = config.get_localized_string(int(name.replace('@','')))
|
if name.startswith('@'): name = config.get_localized_string(int(name.replace('@','')))
|
||||||
it = xbmcgui.ListItem('\n[B]%s[/B] %s - %s' % (name, quality, item.contentTitle))
|
logger.debug(item)
|
||||||
|
it = xbmcgui.ListItem('\n[B]%s[/B] %s - %s [%s]' % (name, quality, item.contentTitle, get_channel_parameters(item.contentChannel)['title']))
|
||||||
it.setArt({'thumb':item.thumbnail})
|
it.setArt({'thumb':item.thumbnail})
|
||||||
options.append(it)
|
options.append(it)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+143
-107
@@ -226,6 +226,45 @@ def dialog_info(item, scraper):
|
|||||||
dialog = TitleOrIDWindow('TitleOrIDWindow.xml', config.get_runtime_path()).Start(item, scraper)
|
dialog = TitleOrIDWindow('TitleOrIDWindow.xml', config.get_runtime_path()).Start(item, scraper)
|
||||||
return dialog
|
return dialog
|
||||||
|
|
||||||
|
def dialog_select_group(heading, _list, preselect=0):
|
||||||
|
class SelectGroup(xbmcgui.WindowXMLDialog):
|
||||||
|
def start(self, heading, _list, preselect):
|
||||||
|
self.selected = preselect
|
||||||
|
self.heading = heading
|
||||||
|
self.list = _list
|
||||||
|
self.doModal()
|
||||||
|
|
||||||
|
return self.selected
|
||||||
|
|
||||||
|
def onInit(self):
|
||||||
|
self.getControl(1).setText(self.heading)
|
||||||
|
itemlist = []
|
||||||
|
for n, text in enumerate(self.list):
|
||||||
|
item = xbmcgui.ListItem(str(n))
|
||||||
|
item.setProperty('title', text)
|
||||||
|
itemlist.append(item)
|
||||||
|
|
||||||
|
self.getControl(2).addItems(itemlist)
|
||||||
|
self.setFocusId(2)
|
||||||
|
self.getControl(2).selectItem(self.selected)
|
||||||
|
|
||||||
|
def onClick(self, control):
|
||||||
|
if control in [100]:
|
||||||
|
self.selected = -1
|
||||||
|
self.close()
|
||||||
|
elif control in [2]:
|
||||||
|
self.selected = self.getControl(2).getSelectedPosition()
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def onAction(self, action):
|
||||||
|
action = action.getId()
|
||||||
|
if action in [10, 92]:
|
||||||
|
self.selected = -1
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
dialog = SelectGroup('SelectGroup.xml', config.get_runtime_path()).start(heading, _list, preselect)
|
||||||
|
return dialog
|
||||||
|
|
||||||
|
|
||||||
def itemlist_refresh():
|
def itemlist_refresh():
|
||||||
# pos = Item().fromurl(xbmc.getInfoLabel('ListItem.FileNameAndPath')).itemlistPosition
|
# pos = Item().fromurl(xbmc.getInfoLabel('ListItem.FileNameAndPath')).itemlistPosition
|
||||||
@@ -616,63 +655,71 @@ def is_playing():
|
|||||||
def play_video(item, strm=False, force_direct=False, autoplay=False):
|
def play_video(item, strm=False, force_direct=False, autoplay=False):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
logger.debug(item.tostring('\n'))
|
logger.debug(item.tostring('\n'))
|
||||||
if item.channel == 'downloads':
|
|
||||||
logger.debug("Play local video: %s [%s]" % (item.title, item.url))
|
|
||||||
xlistitem = xbmcgui.ListItem(path=item.url)
|
|
||||||
xlistitem.setArt({"thumb": item.thumbnail})
|
|
||||||
set_infolabels(xlistitem, item, True)
|
|
||||||
set_player(item, xlistitem, item.url, True, None) # Fix Play From Download Section
|
|
||||||
return
|
|
||||||
|
|
||||||
default_action = config.get_setting("default_action")
|
def play():
|
||||||
logger.debug("default_action=%s" % default_action)
|
if item.channel == 'downloads':
|
||||||
|
logger.debug("Play local video: %s [%s]" % (item.title, item.url))
|
||||||
# pass referer
|
xlistitem = xbmcgui.ListItem(path=item.url)
|
||||||
if item.referer:
|
xlistitem.setArt({"thumb": item.thumbnail})
|
||||||
from core import httptools
|
set_infolabels(xlistitem, item, True)
|
||||||
httptools.default_headers['Referer'] = item.referer
|
set_player(item, xlistitem, item.url, True, None) # Fix Play From Download Section
|
||||||
|
|
||||||
# Open the selection dialog to see the available options
|
|
||||||
opciones, video_urls, seleccion, salir = get_dialogo_opciones(item, default_action, strm, autoplay)
|
|
||||||
if salir: exit()
|
|
||||||
|
|
||||||
# get default option of addon configuration
|
|
||||||
seleccion = get_seleccion(default_action, opciones, seleccion, video_urls)
|
|
||||||
if seleccion < 0: exit() # Canceled box
|
|
||||||
|
|
||||||
logger.debug("selection=%d" % seleccion)
|
|
||||||
logger.debug("selection=%s" % opciones[seleccion])
|
|
||||||
|
|
||||||
# run the available option, jdwonloader, download, favorites, add to the video library ... IF IT IS NOT PLAY
|
|
||||||
salir = set_opcion(item, seleccion, opciones, video_urls)
|
|
||||||
if salir:
|
|
||||||
return
|
|
||||||
|
|
||||||
# we get the selected video
|
|
||||||
mediaurl, view, mpd = get_video_seleccionado(item, seleccion, video_urls, autoplay)
|
|
||||||
if not mediaurl: return
|
|
||||||
|
|
||||||
# video information is obtained.
|
|
||||||
xlistitem = xbmcgui.ListItem(path=item.url)
|
|
||||||
xlistitem.setArt({"thumb": item.contentThumbnail if item.contentThumbnail else item.thumbnail})
|
|
||||||
set_infolabels(xlistitem, item, True)
|
|
||||||
|
|
||||||
# if it is a video in mpd format, the listitem is configured to play it ith the inpustreamaddon addon implemented in Kodi 17
|
|
||||||
# from core.support import dbg;dbg()
|
|
||||||
if mpd:
|
|
||||||
if not install_inputstream():
|
|
||||||
return
|
return
|
||||||
xlistitem.setProperty('inputstream' if PY3 else 'inputstreamaddon', 'inputstream.adaptive')
|
|
||||||
xlistitem.setProperty('inputstream.adaptive.manifest_type', 'mpd')
|
|
||||||
if item.drm and item.license:
|
|
||||||
install_widevine()
|
|
||||||
xlistitem.setProperty("inputstream.adaptive.license_type", item.drm)
|
|
||||||
xlistitem.setProperty("inputstream.adaptive.license_key", item.license)
|
|
||||||
xlistitem.setMimeType('application/dash+xml')
|
|
||||||
|
|
||||||
if force_direct: item.play_from = 'window'
|
default_action = config.get_setting("default_action")
|
||||||
|
logger.debug("default_action=%s" % default_action)
|
||||||
|
|
||||||
set_player(item, xlistitem, mediaurl, view, strm)
|
# pass referer
|
||||||
|
if item.referer:
|
||||||
|
from core import httptools
|
||||||
|
httptools.default_headers['Referer'] = item.referer
|
||||||
|
|
||||||
|
# Open the selection dialog to see the available options
|
||||||
|
opciones, video_urls, seleccion, salir = get_dialogo_opciones(item, default_action, strm, autoplay)
|
||||||
|
if salir: return
|
||||||
|
|
||||||
|
# get default option of addon configuration
|
||||||
|
seleccion = get_seleccion(default_action, opciones, seleccion, video_urls)
|
||||||
|
if seleccion < 0: return # Canceled box
|
||||||
|
|
||||||
|
logger.debug("selection=%d" % seleccion)
|
||||||
|
logger.debug("selection=%s" % opciones[seleccion])
|
||||||
|
|
||||||
|
# run the available option, jdwonloader, download, favorites, add to the video library ... IF IT IS NOT PLAY
|
||||||
|
salir = set_opcion(item, seleccion, opciones, video_urls)
|
||||||
|
if salir:
|
||||||
|
return
|
||||||
|
|
||||||
|
# we get the selected video
|
||||||
|
mediaurl, view, mpd = get_video_seleccionado(item, seleccion, video_urls, autoplay)
|
||||||
|
if not mediaurl: return
|
||||||
|
|
||||||
|
# video information is obtained.
|
||||||
|
xlistitem = xbmcgui.ListItem(path=item.url)
|
||||||
|
xlistitem.setArt({"thumb": item.contentThumbnail if item.contentThumbnail else item.thumbnail})
|
||||||
|
set_infolabels(xlistitem, item, True)
|
||||||
|
|
||||||
|
# if it is a video in mpd format, the listitem is configured to play it ith the inpustreamaddon addon implemented in Kodi 17
|
||||||
|
# from core.support import dbg;dbg()
|
||||||
|
if mpd:
|
||||||
|
if not install_inputstream():
|
||||||
|
return
|
||||||
|
xlistitem.setProperty('inputstream' if PY3 else 'inputstreamaddon', 'inputstream.adaptive')
|
||||||
|
xlistitem.setProperty('inputstream.adaptive.manifest_type', 'mpd')
|
||||||
|
if item.drm and item.license:
|
||||||
|
install_widevine()
|
||||||
|
xlistitem.setProperty("inputstream.adaptive.license_type", item.drm)
|
||||||
|
xlistitem.setProperty("inputstream.adaptive.license_key", item.license)
|
||||||
|
xlistitem.setMimeType('application/dash+xml')
|
||||||
|
|
||||||
|
if force_direct: item.play_from = 'window'
|
||||||
|
|
||||||
|
set_player(item, xlistitem, mediaurl, view, strm)
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not play():
|
||||||
|
# close db to ensure his thread will stop
|
||||||
|
from core import db
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def stop_video():
|
def stop_video():
|
||||||
@@ -1370,71 +1417,60 @@ def get_platform():
|
|||||||
|
|
||||||
|
|
||||||
def get_played_time(item):
|
def get_played_time(item):
|
||||||
import sqlite3
|
logger.debug()
|
||||||
from core import filetools
|
from core import db
|
||||||
db_name = filetools.join(config.get_data_path(), "kod_db.sqlite")
|
|
||||||
ID = item.infoLabels['tmdb_id']
|
if not item.infoLabels:
|
||||||
conn = sqlite3.connect(db_name, timeout=15)
|
return 0
|
||||||
c = conn.cursor()
|
ID = item.infoLabels.get('tmdb_id', '')
|
||||||
c.execute('CREATE TABLE IF NOT EXISTS viewed (tmdb_id TEXT, season INT, episode INT, played_time REAL)')
|
if not ID:
|
||||||
conn.commit()
|
return 0
|
||||||
if ID:
|
|
||||||
if item.contentType == 'movie': c.execute("SELECT played_time FROM viewed WHERE tmdb_id=?", (ID,))
|
S = item.infoLabels.get('season', 0)
|
||||||
elif 'season' in item.infoLabels:
|
E = item.infoLabels.get('episode')
|
||||||
S = item.infoLabels['season']
|
result = None
|
||||||
E = item.infoLabels['episode']
|
|
||||||
c.execute("SELECT played_time FROM viewed WHERE tmdb_id=? AND season=? AND episode=?", (ID, S, E))
|
if item.contentType == 'movie':
|
||||||
elif 'episode' in item.infoLabels:
|
result = db['viewed'].get(ID)
|
||||||
E = item.infoLabels['episode']
|
elif S and E:
|
||||||
c.execute("SELECT played_time FROM viewed WHERE tmdb_id=? AND episode=?", (ID, E))
|
result = db['viewed'].get(ID, {}).get(str(S)+'x'+str(E))
|
||||||
result = c.fetchone()
|
|
||||||
if not result: played_time = 0
|
if not result: played_time = 0
|
||||||
else: played_time = result[0]
|
else: played_time = result
|
||||||
else: played_time = 0
|
|
||||||
conn.close()
|
|
||||||
return played_time
|
return played_time
|
||||||
|
|
||||||
|
|
||||||
def set_played_time(item):
|
def set_played_time(item):
|
||||||
import sqlite3
|
logger.debug()
|
||||||
from core import filetools
|
from core import db
|
||||||
ID = item.infoLabels['tmdb_id']
|
|
||||||
played_time = item.played_time
|
played_time = item.played_time
|
||||||
db_name = filetools.join(config.get_data_path(), "kod_db.sqlite")
|
if not item.infoLabels:
|
||||||
conn = sqlite3.connect(db_name, timeout=15)
|
return
|
||||||
c = conn.cursor()
|
|
||||||
|
ID = item.infoLabels.get('tmdb_id', '')
|
||||||
|
if not ID:
|
||||||
|
return
|
||||||
|
|
||||||
|
S = item.infoLabels.get('season', 0)
|
||||||
|
E = item.infoLabels.get('episode')
|
||||||
|
|
||||||
|
|
||||||
if item.contentType == 'movie':
|
if item.contentType == 'movie':
|
||||||
c.execute("SELECT played_time FROM viewed WHERE tmdb_id=?", (ID,))
|
db['viewed'][ID] = played_time
|
||||||
result = c.fetchone()
|
elif E:
|
||||||
if result:
|
newDict = db['viewed'].get(ID, {})
|
||||||
if played_time > 0: c.execute("UPDATE viewed SET played_time=? WHERE tmdb_id=?", (item.played_time, ID))
|
newDict[str(S) + 'x' + str(E)] = played_time
|
||||||
else: c.execute("DELETE from viewed WHERE tmdb_id=?", (ID,))
|
db['viewed'][ID] = newDict
|
||||||
else: c.execute("INSERT INTO viewed (tmdb_id, played_time) VALUES (?, ?)", (ID, item.played_time))
|
|
||||||
elif 'season' in item.infoLabels:
|
|
||||||
S = item.infoLabels['season']
|
|
||||||
E = item.infoLabels['episode']
|
|
||||||
c.execute("SELECT played_time FROM viewed WHERE tmdb_id=? AND season = ? AND episode=?", (ID, S, E))
|
|
||||||
result = c.fetchone()
|
|
||||||
if result:
|
|
||||||
if played_time > 0: c.execute("UPDATE viewed SET played_time=? WHERE tmdb_id=? AND season=? AND episode=?", (item.played_time, ID, S, E))
|
|
||||||
else: c.execute("DELETE from viewed WHERE tmdb_id=? AND season=? AND episode=?", (ID, S, E))
|
|
||||||
else: c.execute("INSERT INTO viewed (tmdb_id, season, episode, played_time) VALUES (?, ?, ?, ?)", (ID, S, E, item.played_time))
|
|
||||||
elif 'episode' in item.infoLabels:
|
|
||||||
E = item.infoLabels['episode']
|
|
||||||
c.execute("SELECT played_time FROM viewed WHERE tmdb_id=? AND episode=?", (ID, E))
|
|
||||||
result = c.fetchone()
|
|
||||||
if result:
|
|
||||||
if played_time > 0: c.execute("UPDATE viewed SET played_time=? WHERE tmdb_id=? AND episode=?", (item.played_time, ID, E))
|
|
||||||
else: c.execute("DELETE from viewed WHERE tmdb_id=? AND episode=?", (ID, E))
|
|
||||||
else: c.execute("INSERT INTO viewed (tmdb_id, episode, played_time) VALUES (?, ?, ?)", (ID, E, item.played_time))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
def prevent_busy(item):
|
def prevent_busy(item):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
if not item.autoplay and not item.window:
|
if not item.autoplay and not item.window:
|
||||||
if item.globalsearch: xbmc.Player().play(os.path.join(config.get_runtime_path(), "resources", "kod.mp4"))
|
if item.globalsearch: xbmc.Player().play(os.path.join(config.get_runtime_path(), "resources", "kod.mp4"))
|
||||||
else: xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, xbmcgui.ListItem(path=os.path.join(config.get_runtime_path(), "resources", "kod.mp4")))
|
else: xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, xbmcgui.ListItem(path=os.path.join(config.get_runtime_path(), "resources", "kod.mp4")))
|
||||||
xbmc.sleep(100)
|
xbmc.sleep(200)
|
||||||
xbmc.Player().stop()
|
xbmc.Player().stop()
|
||||||
# xbmc.executebuiltin('Action(Stop)')
|
# xbmc.executebuiltin('Action(Stop)')
|
||||||
# xbmc.sleep(500)
|
# xbmc.sleep(500)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ def mark_auto_as_watched(item):
|
|||||||
ND = next_dialogs[next_ep_type]
|
ND = next_dialogs[next_ep_type]
|
||||||
try: next_episode = next_ep(item)
|
try: next_episode = next_ep(item)
|
||||||
except: next_episode = False
|
except: next_episode = False
|
||||||
|
logger.debug(next_episode)
|
||||||
|
|
||||||
while platformtools.is_playing():
|
while platformtools.is_playing():
|
||||||
actual_time = xbmc.Player().getTime()
|
actual_time = xbmc.Player().getTime()
|
||||||
@@ -108,9 +109,6 @@ def mark_auto_as_watched(item):
|
|||||||
threading.Thread(target=mark_as_watched_subThread, args=[item]).start()
|
threading.Thread(target=mark_as_watched_subThread, args=[item]).start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def sync_trakt_addon(path_folder):
|
def sync_trakt_addon(path_folder):
|
||||||
"""
|
"""
|
||||||
Updates the values of episodes seen if
|
Updates the values of episodes seen if
|
||||||
@@ -351,6 +349,38 @@ def mark_season_as_watched_on_kodi(item, value=1):
|
|||||||
|
|
||||||
execute_sql_kodi(sql)
|
execute_sql_kodi(sql)
|
||||||
|
|
||||||
|
def set_watched_on_kod(data):
|
||||||
|
from specials import videolibrary
|
||||||
|
from core import videolibrarytools
|
||||||
|
data = jsontools.load(data)
|
||||||
|
Type = data.get('item', {}).get('type','')
|
||||||
|
ID = data.get('item', {}).get('id','')
|
||||||
|
if not Type or not ID:
|
||||||
|
return
|
||||||
|
playcount = data.get('playcount',0)
|
||||||
|
for Type in ['movie', 'episode']:
|
||||||
|
sql = 'select strFileName, strPath, uniqueid_value from %s_view where (id%s like "%s")' % (Type, Type.capitalize(), ID)
|
||||||
|
n, records = execute_sql_kodi(sql)
|
||||||
|
if records:
|
||||||
|
for filename, path, uniqueid_value in records:
|
||||||
|
if Type in ['movie']:
|
||||||
|
title = filename.replace('.strm', ' [' + uniqueid_value + ']')
|
||||||
|
filename = title +'.nfo'
|
||||||
|
else:
|
||||||
|
title = filename.replace('.strm', '')
|
||||||
|
filename = 'tvshow.nfo'
|
||||||
|
|
||||||
|
path = filetools.join(path, filename)
|
||||||
|
head_nfo, item = videolibrarytools.read_nfo(path)
|
||||||
|
item.library_playcounts.update({title: playcount})
|
||||||
|
filetools.write(path, head_nfo + item.tojson())
|
||||||
|
|
||||||
|
if item.infoLabels['mediatype'] == "tvshow":
|
||||||
|
for season in item.library_playcounts:
|
||||||
|
if "season" in season:
|
||||||
|
season_num = int(scrapertools.find_single_match(season, r'season (\d+)'))
|
||||||
|
item = videolibrary.check_season_playcount(item, season_num)
|
||||||
|
filetools.write(path, head_nfo + item.tojson())
|
||||||
|
|
||||||
def mark_content_as_watched_on_kod(path):
|
def mark_content_as_watched_on_kod(path):
|
||||||
from specials import videolibrary
|
from specials import videolibrary
|
||||||
@@ -384,6 +414,7 @@ def mark_content_as_watched_on_kod(path):
|
|||||||
if "\\" in path:
|
if "\\" in path:
|
||||||
path = path.replace("/", "\\")
|
path = path.replace("/", "\\")
|
||||||
head_nfo, item = videolibrarytools.read_nfo(path) # I read the content .nfo
|
head_nfo, item = videolibrarytools.read_nfo(path) # I read the content .nfo
|
||||||
|
old = item.clone()
|
||||||
if not item:
|
if not item:
|
||||||
logger.error('.NFO not found: ' + path)
|
logger.error('.NFO not found: ' + path)
|
||||||
return
|
return
|
||||||
@@ -437,8 +468,9 @@ def mark_content_as_watched_on_kod(path):
|
|||||||
if "season" in season: # we look for the tags "season" inside playCounts
|
if "season" in season: # we look for the tags "season" inside playCounts
|
||||||
season_num = int(scrapertools.find_single_match(season, r'season (\d+)')) # we save the season number
|
season_num = int(scrapertools.find_single_match(season, r'season (\d+)')) # we save the season number
|
||||||
item = videolibrary.check_season_playcount(item, season_num) # We call the method that updates Temps. and series
|
item = videolibrary.check_season_playcount(item, season_num) # We call the method that updates Temps. and series
|
||||||
|
if item.library_playcounts != old.library_playcounts:
|
||||||
filetools.write(path, head_nfo + item.tojson())
|
logger.debug('scrivo')
|
||||||
|
filetools.write(path, head_nfo + item.tojson())
|
||||||
|
|
||||||
#logger.debug(item)
|
#logger.debug(item)
|
||||||
|
|
||||||
@@ -626,37 +658,14 @@ def set_content(content_type, silent=False, custom=False):
|
|||||||
xbmc.executebuiltin('Addon.OpenSettings(metadata.universal)', True)
|
xbmc.executebuiltin('Addon.OpenSettings(metadata.universal)', True)
|
||||||
|
|
||||||
else: # SERIES
|
else: # SERIES
|
||||||
scraper = [config.get_localized_string(70098), config.get_localized_string(70093)]
|
scraper = [config.get_localized_string(70093), config.get_localized_string(70098)]
|
||||||
if not custom:
|
if not custom:
|
||||||
seleccion = 0 # tvdb
|
seleccion = 0 # tmdb
|
||||||
else:
|
else:
|
||||||
seleccion = platformtools.dialog_select(config.get_localized_string(70107), scraper)
|
seleccion = platformtools.dialog_select(config.get_localized_string(70107), scraper)
|
||||||
|
|
||||||
# Instalar The TVDB
|
|
||||||
if seleccion == -1 or seleccion == 0:
|
|
||||||
if not xbmc.getCondVisibility('System.HasAddon(metadata.tvdb.com)'):
|
|
||||||
if not silent:
|
|
||||||
#Ask if we want to install metadata.tvdb.com
|
|
||||||
install = platformtools.dialog_yesno(config.get_localized_string(60048),'')
|
|
||||||
else:
|
|
||||||
install = True
|
|
||||||
|
|
||||||
if install:
|
|
||||||
try:
|
|
||||||
# Install metadata.tvdb.com
|
|
||||||
xbmc.executebuiltin('InstallAddon(metadata.tvdb.com)', True)
|
|
||||||
logger.debug("The TVDB series Scraper installed ")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
continuar = (install and xbmc.getCondVisibility('System.HasAddon(metadata.tvdb.com)'))
|
|
||||||
if not continuar:
|
|
||||||
msg_text = config.get_localized_string(60049)
|
|
||||||
if continuar:
|
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(metadata.tvdb.com)', True)
|
|
||||||
|
|
||||||
# Instalar The Movie Database
|
# Instalar The Movie Database
|
||||||
elif seleccion == 1:
|
if seleccion == -1 or seleccion == 0:
|
||||||
if continuar and not xbmc.getCondVisibility('System.HasAddon(metadata.tvshows.themoviedb.org)'):
|
if continuar and not xbmc.getCondVisibility('System.HasAddon(metadata.tvshows.themoviedb.org)'):
|
||||||
continuar = False
|
continuar = False
|
||||||
if not silent:
|
if not silent:
|
||||||
@@ -680,6 +689,29 @@ def set_content(content_type, silent=False, custom=False):
|
|||||||
if continuar:
|
if continuar:
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(metadata.tvshows.themoviedb.org)', True)
|
xbmc.executebuiltin('Addon.OpenSettings(metadata.tvshows.themoviedb.org)', True)
|
||||||
|
|
||||||
|
# Instalar The TVDB
|
||||||
|
elif seleccion == 1:
|
||||||
|
if not xbmc.getCondVisibility('System.HasAddon(metadata.tvdb.com)'):
|
||||||
|
if not silent:
|
||||||
|
#Ask if we want to install metadata.tvdb.com
|
||||||
|
install = platformtools.dialog_yesno(config.get_localized_string(60048),'')
|
||||||
|
else:
|
||||||
|
install = True
|
||||||
|
|
||||||
|
if install:
|
||||||
|
try:
|
||||||
|
# Install metadata.tvdb.com
|
||||||
|
xbmc.executebuiltin('InstallAddon(metadata.tvdb.com)', True)
|
||||||
|
logger.debug("The TVDB series Scraper installed ")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
continuar = (install and xbmc.getCondVisibility('System.HasAddon(metadata.tvdb.com)'))
|
||||||
|
if not continuar:
|
||||||
|
msg_text = config.get_localized_string(60049)
|
||||||
|
if continuar:
|
||||||
|
xbmc.executebuiltin('Addon.OpenSettings(metadata.tvdb.com)', True)
|
||||||
|
|
||||||
idPath = 0
|
idPath = 0
|
||||||
idParentPath = 0
|
idParentPath = 0
|
||||||
if continuar:
|
if continuar:
|
||||||
@@ -753,11 +785,11 @@ def set_content(content_type, silent=False, custom=False):
|
|||||||
strContent = 'tvshows'
|
strContent = 'tvshows'
|
||||||
scanRecursive = 0
|
scanRecursive = 0
|
||||||
if seleccion == -1 or seleccion == 0:
|
if seleccion == -1 or seleccion == 0:
|
||||||
strScraper = 'metadata.tvdb.com'
|
|
||||||
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.tvdb.com/settings.xml")
|
|
||||||
elif seleccion == 1:
|
|
||||||
strScraper = 'metadata.tvshows.themoviedb.org'
|
strScraper = 'metadata.tvshows.themoviedb.org'
|
||||||
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.tvshows.themoviedb.org/settings.xml")
|
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.tvshows.themoviedb.org/settings.xml")
|
||||||
|
elif seleccion == 1:
|
||||||
|
strScraper = 'metadata.tvdb.com'
|
||||||
|
path_settings = xbmc.translatePath("special://profile/addon_data/metadata.tvdb.com/settings.xml")
|
||||||
if not os.path.exists(path_settings):
|
if not os.path.exists(path_settings):
|
||||||
logger.debug("%s: %s" % (content_type, path_settings + " doesn't exist"))
|
logger.debug("%s: %s" % (content_type, path_settings + " doesn't exist"))
|
||||||
return continuar
|
return continuar
|
||||||
@@ -961,13 +993,14 @@ def clean(path_list=[]):
|
|||||||
sql = 'SELECT idPath FROM path where strPath LIKE "%s"' % sql_path
|
sql = 'SELECT idPath FROM path where strPath LIKE "%s"' % sql_path
|
||||||
logger.debug('sql: ' + sql)
|
logger.debug('sql: ' + sql)
|
||||||
nun_records, records = execute_sql_kodi(sql)
|
nun_records, records = execute_sql_kodi(sql)
|
||||||
idPath = records[0][0]
|
if records:
|
||||||
sql = 'DELETE from path WHERE idPath=%s' % idPath
|
idPath = records[0][0]
|
||||||
logger.debug('sql: ' + sql)
|
sql = 'DELETE from path WHERE idPath=%s' % idPath
|
||||||
nun_records, records = execute_sql_kodi(sql)
|
logger.debug('sql: ' + sql)
|
||||||
sql = 'DELETE from path WHERE idParentPath=%s' % idPath
|
nun_records, records = execute_sql_kodi(sql)
|
||||||
logger.debug('sql: ' + sql)
|
sql = 'DELETE from path WHERE idParentPath=%s' % idPath
|
||||||
nun_records, records = execute_sql_kodi(sql)
|
logger.debug('sql: ' + sql)
|
||||||
|
nun_records, records = execute_sql_kodi(sql)
|
||||||
|
|
||||||
from core import videolibrarytools
|
from core import videolibrarytools
|
||||||
for path, folders, files in filetools.walk(videolibrarytools.MOVIES_PATH):
|
for path, folders, files in filetools.walk(videolibrarytools.MOVIES_PATH):
|
||||||
@@ -1236,7 +1269,6 @@ def update_sources(new='', old=''):
|
|||||||
def ask_set_content(silent=False):
|
def ask_set_content(silent=False):
|
||||||
logger.debug()
|
logger.debug()
|
||||||
logger.debug("videolibrary_kodi %s" % config.get_setting("videolibrary_kodi"))
|
logger.debug("videolibrary_kodi %s" % config.get_setting("videolibrary_kodi"))
|
||||||
|
|
||||||
def do_config(custom=False):
|
def do_config(custom=False):
|
||||||
if set_content("movie", True, custom) and set_content("tvshow", True, custom):
|
if set_content("movie", True, custom) and set_content("tvshow", True, custom):
|
||||||
platformtools.dialog_ok(config.get_localized_string(80026), config.get_localized_string(70104))
|
platformtools.dialog_ok(config.get_localized_string(80026), config.get_localized_string(70104))
|
||||||
@@ -1271,11 +1303,11 @@ def ask_set_content(silent=False):
|
|||||||
config.set_setting("folder_movies", movies_folder)
|
config.set_setting("folder_movies", movies_folder)
|
||||||
config.set_setting("folder_tvshows", tvshows_folder)
|
config.set_setting("folder_tvshows", tvshows_folder)
|
||||||
config.verify_directories_created()
|
config.verify_directories_created()
|
||||||
do_config(True)
|
do_config(False)
|
||||||
# default path and folders
|
# default path and folders
|
||||||
else:
|
else:
|
||||||
platformtools.dialog_ok(config.get_localized_string(80026), config.get_localized_string(80030))
|
platformtools.dialog_ok(config.get_localized_string(80026), config.get_localized_string(80030))
|
||||||
do_config(True)
|
do_config(False)
|
||||||
# default settings
|
# default settings
|
||||||
else:
|
else:
|
||||||
platformtools.dialog_ok(config.get_localized_string(80026), config.get_localized_string(80027))
|
platformtools.dialog_ok(config.get_localized_string(80026), config.get_localized_string(80027))
|
||||||
@@ -1286,7 +1318,7 @@ def ask_set_content(silent=False):
|
|||||||
# configuration from the settings menu
|
# configuration from the settings menu
|
||||||
else:
|
else:
|
||||||
platformtools.dialog_ok(config.get_localized_string(80026), config.get_localized_string(80023))
|
platformtools.dialog_ok(config.get_localized_string(80026), config.get_localized_string(80023))
|
||||||
do_config(True)
|
do_config(False)
|
||||||
|
|
||||||
|
|
||||||
def next_ep(item):
|
def next_ep(item):
|
||||||
|
|||||||
Binary file not shown.
@@ -701,7 +701,7 @@ msgid "This website [B]%s[/B] seems to be unavailable, try later. if the web pag
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#60014"
|
msgctxt "#60014"
|
||||||
msgid "It may be due to a connection problem, the web page of the channel has changed its structure, or an internal error of KoD. If on browser it works, report the issue using [B]Help->Report an issue.[B]"
|
msgid "It may be due to a connection problem, the web page of the channel has changed its structure, or an internal error of KoD. If on browser it works, report the issue using [B]Help->Report an issue.[/B]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#60015"
|
msgctxt "#60015"
|
||||||
@@ -6123,6 +6123,9 @@ msgctxt "#70830"
|
|||||||
msgid "The series / episode number should only be changed if the series has relative numbering."
|
msgid "The series / episode number should only be changed if the series has relative numbering."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgctxt "#70831"
|
||||||
|
msgid "This series has multiple types of numbering, choose the most suitable one "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
# DNS start [ settings and declaration ]
|
# DNS start [ settings and declaration ]
|
||||||
msgctxt "#707401"
|
msgctxt "#707401"
|
||||||
@@ -6370,7 +6373,7 @@ msgid "Configuration of Kodi video library"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#80027"
|
msgctxt "#80027"
|
||||||
msgid "You will be asked to configure The Movie Database for movies and The TVDB for TV shows"
|
msgid "You will be asked to configure The Movie Database for movies and tvshows"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#80028"
|
msgctxt "#80028"
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ msgid "This website [B]%s[/B] seems to be unavailable, try later. if the web pag
|
|||||||
msgstr "Il sito [B]%s[/B] non sembra essere disponibile, riprova più tardi. Se la pagina web funziona correttamente segnala l'errore qui: https://github.com/kodiondemand/addon/issues"
|
msgstr "Il sito [B]%s[/B] non sembra essere disponibile, riprova più tardi. Se la pagina web funziona correttamente segnala l'errore qui: https://github.com/kodiondemand/addon/issues"
|
||||||
|
|
||||||
msgctxt "#60014"
|
msgctxt "#60014"
|
||||||
msgid "It may be due to a connection problem, the web page of the channel has changed its structure, or an internal error of KoD. If on browser it works, report the issue using [B]Help->Report an issue.[B]"
|
msgid "It may be due to a connection problem, the web page of the channel has changed its structure, or an internal error of KoD. If on browser it works, report the issue using [B]Help->Report an issue.[/B]"
|
||||||
msgstr "Potrebbe essere dovuto a un problema di connessione, la pagina web del canale ha cambiato la sua struttura, oppure un errore interno di KoD. Se sul browser funziona, segnala il problema andando in [B]Aiuto->Segnala un problema[/B]."
|
msgstr "Potrebbe essere dovuto a un problema di connessione, la pagina web del canale ha cambiato la sua struttura, oppure un errore interno di KoD. Se sul browser funziona, segnala il problema andando in [B]Aiuto->Segnala un problema[/B]."
|
||||||
|
|
||||||
msgctxt "#60015"
|
msgctxt "#60015"
|
||||||
@@ -6124,6 +6124,10 @@ msgctxt "#70830"
|
|||||||
msgid "The series / episode number should only be changed if the series has relative numbering."
|
msgid "The series / episode number should only be changed if the series has relative numbering."
|
||||||
msgstr "Il numero della serie / episodio deve essere modificato solo se la serie ha una numerazione relativa."
|
msgstr "Il numero della serie / episodio deve essere modificato solo se la serie ha una numerazione relativa."
|
||||||
|
|
||||||
|
msgctxt "#70831"
|
||||||
|
msgid "This series has multiple types of numbering, choose the most suitable one "
|
||||||
|
msgstr "Questa serie ha più tipi di numerazione, scegli quello più adatto"
|
||||||
|
|
||||||
# DNS start [ settings and declaration ]
|
# DNS start [ settings and declaration ]
|
||||||
msgctxt "#707401"
|
msgctxt "#707401"
|
||||||
msgid "Enable DNS check alert"
|
msgid "Enable DNS check alert"
|
||||||
@@ -6370,8 +6374,8 @@ msgid "Configuration of Kodi video library"
|
|||||||
msgstr "Configurazione della libreria di Kodi"
|
msgstr "Configurazione della libreria di Kodi"
|
||||||
|
|
||||||
msgctxt "#80027"
|
msgctxt "#80027"
|
||||||
msgid "You will be asked to configure The Movie Database for movies and The TVDB for TV shows"
|
msgid "You will be asked to configure The Movie Database for movies and tvshows"
|
||||||
msgstr "Ti verrà chiesto di configurare The Movie Database per i film e The TVDB per le serie TV"
|
msgstr "Ti verrà chiesto di configurare The Movie Database sia per i film che per le serie TV"
|
||||||
|
|
||||||
msgctxt "#80028"
|
msgctxt "#80028"
|
||||||
msgid "The selected folders are already used by the Kodi library. Please change them properly"
|
msgid "The selected folders are already used by the Kodi library. Please change them properly"
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<window>
|
||||||
|
<allowoverlays>false</allowoverlays>
|
||||||
|
<animation type="WindowOpen" reversible="false">
|
||||||
|
<effect type="slide" start="0,200" end="0,0" center="640,225" delay="160" tween="cubic" time="200" />
|
||||||
|
<effect type="fade" delay="160" end="100" time="240" />
|
||||||
|
</animation>
|
||||||
|
<animation type="WindowClose" reversible="false">
|
||||||
|
<effect type="slide" start="0,0" end="0,200" center="640,225" easing="in" tween="cubic" time="200" />
|
||||||
|
<effect type="fade" start="100" end="0" time="240" />
|
||||||
|
</animation>
|
||||||
|
<controls>
|
||||||
|
<control type="group">
|
||||||
|
<description>Container</description>
|
||||||
|
<left>200</left>
|
||||||
|
<top>60</top>
|
||||||
|
<width>860</width>
|
||||||
|
<height>600</height>
|
||||||
|
<control type="image">
|
||||||
|
<description>Background</description>
|
||||||
|
<width>100%</width>
|
||||||
|
<height>100%</height>
|
||||||
|
<texture colordiffuse="FF232323">white.png</texture>
|
||||||
|
</control>
|
||||||
|
<control type="textbox" id="1">
|
||||||
|
<description>Heading</description>
|
||||||
|
<top>0</top>
|
||||||
|
<left>0</left>
|
||||||
|
<height>60</height>
|
||||||
|
<width>100%</width>
|
||||||
|
<font>font13</font>
|
||||||
|
<textcolor>FFFFFFFF</textcolor>
|
||||||
|
<align>center</align>
|
||||||
|
<aligny>center</aligny>
|
||||||
|
<label></label>
|
||||||
|
</control>
|
||||||
|
<control type="group">
|
||||||
|
<left>30</left>
|
||||||
|
<top>70</top>
|
||||||
|
<width>595</width>
|
||||||
|
<height>530</height>
|
||||||
|
<control type="list" id="2">
|
||||||
|
<description>List</description>
|
||||||
|
<left>0</left>
|
||||||
|
<top>0</top>
|
||||||
|
<width>795</width>
|
||||||
|
<height>530</height>
|
||||||
|
<onup>100</onup>
|
||||||
|
<onright>4</onright>
|
||||||
|
<orientation>vertical</orientation>
|
||||||
|
<scrolltime>200</scrolltime>
|
||||||
|
<pagecontrol>4</pagecontrol>
|
||||||
|
<itemlayout height="150" width="795">
|
||||||
|
<control type="textbox">
|
||||||
|
<description>Selected Item</description>
|
||||||
|
<left>20</left>
|
||||||
|
<top>20</top>
|
||||||
|
<width>765</width>
|
||||||
|
<height>110</height>
|
||||||
|
<font>font13</font>
|
||||||
|
<textcolor>FFFFFFFF</textcolor>
|
||||||
|
<label>$INFO[ListItem.Property(title)]</label>
|
||||||
|
<align>center</align>
|
||||||
|
<aligny>center</aligny>
|
||||||
|
</control>
|
||||||
|
</itemlayout>
|
||||||
|
<focusedlayout height="150" width="795">
|
||||||
|
<control type="image">
|
||||||
|
<top>1</top>
|
||||||
|
<width>100%</width>
|
||||||
|
<height>100%</height>
|
||||||
|
<texture colordiffuse="FF0081C2">white.png</texture>
|
||||||
|
</control>
|
||||||
|
<control type="textbox">
|
||||||
|
<description>Selected Item</description>
|
||||||
|
<left>20</left>
|
||||||
|
<top>20</top>
|
||||||
|
<width>765</width>
|
||||||
|
<height>110</height>
|
||||||
|
<font>font13</font>
|
||||||
|
<textcolor>FFFFFFFF</textcolor>
|
||||||
|
<label>$INFO[ListItem.Property(title)]</label>
|
||||||
|
<autoscroll time="3000" delay="3000" repeat="3000">True</autoscroll>
|
||||||
|
<align>center</align>
|
||||||
|
<aligny>center</aligny>
|
||||||
|
</control>
|
||||||
|
</focusedlayout>
|
||||||
|
</control>
|
||||||
|
<control type="scrollbar" id="4">
|
||||||
|
<description>Scrollbar</description>
|
||||||
|
<left>595</left>
|
||||||
|
<top>60</top>
|
||||||
|
<width>5</width>
|
||||||
|
<height>100%</height>
|
||||||
|
<visible>true</visible>
|
||||||
|
<texturesliderbackground colordiffuse="FF232323">white.png</texturesliderbackground>
|
||||||
|
<texturesliderbar colordiffuse="FFFFFFFF">white.png</texturesliderbar>
|
||||||
|
<texturesliderbarfocus colordiffuse="FF0081C2">white.png</texturesliderbarfocus>
|
||||||
|
<textureslidernib colordiffuse="FF232323">white.png</textureslidernib>
|
||||||
|
<textureslidernibfocus colordiffuse="FF232323">white.png</textureslidernibfocus>
|
||||||
|
<orientation>vertical</orientation>
|
||||||
|
<showonepage>false</showonepage>
|
||||||
|
<onleft>2</onleft>
|
||||||
|
<onright>3</onright>
|
||||||
|
</control>
|
||||||
|
</control>
|
||||||
|
<control type="button" id="100">
|
||||||
|
<description>Close Button</description>
|
||||||
|
<right>30</right>
|
||||||
|
<top>10</top>
|
||||||
|
<width>40</width>
|
||||||
|
<height>40</height>
|
||||||
|
<texturefocus colordiffuse="FFFFFFFF">close.png</texturefocus>
|
||||||
|
<texturenofocus colordiffuse="55FFFFFF">close.png</texturenofocus>
|
||||||
|
<ondown>2</ondown>
|
||||||
|
</control>
|
||||||
|
</control>
|
||||||
|
</controls>
|
||||||
|
</window>
|
||||||
Binary file not shown.
@@ -18,6 +18,7 @@ def test_video_exists(page_url):
|
|||||||
|
|
||||||
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
def get_video_url(page_url, premium=False, user="", password="", video_password=""):
|
||||||
logger.debug("url=" + page_url)
|
logger.debug("url=" + page_url)
|
||||||
|
# from core.support import dbg;dbg()
|
||||||
qualities = []
|
qualities = []
|
||||||
video_urls = []
|
video_urls = []
|
||||||
mgid = support.match(data, patron=r'uri":"([^"]+)"').match
|
mgid = support.match(data, patron=r'uri":"([^"]+)"').match
|
||||||
@@ -26,8 +27,9 @@ def get_video_url(page_url, premium=False, user="", password="", video_password=
|
|||||||
url = jsontools.load(support.match(rootUrl.replace('&device={device}','').format(uri = ID)).data)['package']['video']['item'][0]['rendition'][0]['src']
|
url = jsontools.load(support.match(rootUrl.replace('&device={device}','').format(uri = ID)).data)['package']['video']['item'][0]['rendition'][0]['src']
|
||||||
urls = support.match(url, patron=r'RESOLUTION=(\d+x\d+).*?(http[^ ]+)').matches
|
urls = support.match(url, patron=r'RESOLUTION=(\d+x\d+).*?(http[^ ]+)').matches
|
||||||
for quality, url in urls:
|
for quality, url in urls:
|
||||||
|
quality = quality.split('x')[0]
|
||||||
if quality not in qualities:
|
if quality not in qualities:
|
||||||
qualities.append(quality)
|
qualities.append(quality)
|
||||||
video_urls.append(["m3u8 {}p [Paramount]".format(quality.split('x')[-1]), url])
|
video_urls.append(["m3u8 {}p [Paramount]".format(quality), url])
|
||||||
video_urls.sort(key=lambda url: int(support.match(url[0], patron=r'(\d+)p').match))
|
video_urls.sort(key=lambda url: int(support.match(url[0], patron=r'(\d+)p').match))
|
||||||
return video_urls
|
return video_urls
|
||||||
|
|||||||
+8
-5
@@ -43,11 +43,14 @@ def get_video_url(page_url, premium=False, user="", password="", video_password=
|
|||||||
addon_dir = xbmc.translatePath( my_addon.getAddonInfo('path') )
|
addon_dir = xbmc.translatePath( my_addon.getAddonInfo('path') )
|
||||||
sys.path.append(filetools.join( addon_dir, 'resources', 'lib' ) )
|
sys.path.append(filetools.join( addon_dir, 'resources', 'lib' ) )
|
||||||
from youtube_resolver import resolve
|
from youtube_resolver import resolve
|
||||||
for stream in resolve(page_url):
|
try:
|
||||||
# title = scrapertools.find_single_match(stream['title'], '(\d+p)')
|
for stream in resolve(page_url):
|
||||||
if scrapertools.find_single_match(stream['title'], r'(\d+p)'):
|
# title = scrapertools.find_single_match(stream['title'], '(\d+p)')
|
||||||
video_urls.append([re.sub(r'(\[[^\]]+\])', '', stream['title']), stream['url']])
|
if scrapertools.find_single_match(stream['title'], r'(\d+p)'):
|
||||||
video_urls.sort(key=lambda it: int(it[0].split("p", 1)[0]))
|
video_urls.append([re.sub(r'(\[[^\]]+\])', '', stream['title']), stream['url']])
|
||||||
|
video_urls.sort(key=lambda it: int(it[0].split("p", 1)[0]))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return video_urls
|
return video_urls
|
||||||
|
|
||||||
|
|||||||
+61
-1
@@ -24,7 +24,7 @@ sys.path.insert(0, librerias)
|
|||||||
|
|
||||||
from core import videolibrarytools, filetools, channeltools, httptools, scrapertools
|
from core import videolibrarytools, filetools, channeltools, httptools, scrapertools
|
||||||
from lib import schedule
|
from lib import schedule
|
||||||
from platformcode import logger, platformtools, updater
|
from platformcode import logger, platformtools, updater, xbmc_videolibrary
|
||||||
from specials import videolibrary
|
from specials import videolibrary
|
||||||
from servers import torrent
|
from servers import torrent
|
||||||
|
|
||||||
@@ -398,6 +398,11 @@ class AddonMonitor(xbmc.Monitor):
|
|||||||
xbmc.executebuiltin('Action(reloadkeymaps)')
|
xbmc.executebuiltin('Action(reloadkeymaps)')
|
||||||
self.settings_pre = settings_post
|
self.settings_pre = settings_post
|
||||||
|
|
||||||
|
def onNotification(self, sender, method, data):
|
||||||
|
if method == 'VideoLibrary.OnUpdate':
|
||||||
|
xbmc_videolibrary.set_watched_on_kod(data)
|
||||||
|
logger.debug('AGGIORNO')
|
||||||
|
|
||||||
def onScreensaverActivated(self):
|
def onScreensaverActivated(self):
|
||||||
logger.debug('screensaver activated, un-scheduling screen-on jobs')
|
logger.debug('screensaver activated, un-scheduling screen-on jobs')
|
||||||
schedule.clear('screenOn')
|
schedule.clear('screenOn')
|
||||||
@@ -446,6 +451,61 @@ if __name__ == "__main__":
|
|||||||
# handling old autoexec method
|
# handling old autoexec method
|
||||||
if config.is_autorun_enabled():
|
if config.is_autorun_enabled():
|
||||||
config.enable_disable_autorun(True)
|
config.enable_disable_autorun(True)
|
||||||
|
# port old db to new
|
||||||
|
old_db_name = filetools.join(config.get_data_path(), "kod_db.sqlite")
|
||||||
|
if filetools.isfile(old_db_name):
|
||||||
|
try:
|
||||||
|
import sqlite3
|
||||||
|
from core import db
|
||||||
|
|
||||||
|
old_db_conn = sqlite3.connect(old_db_name, timeout=15)
|
||||||
|
old_db = old_db_conn.cursor()
|
||||||
|
old_db.execute('select * from viewed')
|
||||||
|
|
||||||
|
for ris in old_db.fetchall():
|
||||||
|
if ris[1]: # tvshow
|
||||||
|
show = db['viewed'].get(ris[0], {})
|
||||||
|
show[str(ris[1]) + 'x' + str(ris[2])] = ris[3]
|
||||||
|
db['viewed'][ris[0]] = show
|
||||||
|
else: # film
|
||||||
|
db['viewed'][ris[0]] = ris[3]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
filetools.remove(old_db_name, True, False)
|
||||||
|
|
||||||
|
# replace tvdb to tmdb for series
|
||||||
|
if config.get_setting('videolibrary_kodi') and config.get_setting('show_once'):
|
||||||
|
nun_records, records = xbmc_videolibrary.execute_sql_kodi('select * from path where strPath like "' +
|
||||||
|
filetools.join(config.get_setting('videolibrarypath'), config.get_setting('folder_tvshows')) +
|
||||||
|
'%" and strScraper="metadata.tvdb.com"')
|
||||||
|
if nun_records:
|
||||||
|
import xbmcaddon
|
||||||
|
# change language
|
||||||
|
tvdbLang = xbmcaddon.Addon(id="metadata.tvdb.com").getSetting('language')
|
||||||
|
newLang = tvdbLang + '-' + tvdbLang.upper()
|
||||||
|
xbmcaddon.Addon(id="metadata.tvshows.themoviedb.org").setSetting('language', newLang)
|
||||||
|
updater.refreshLang()
|
||||||
|
|
||||||
|
# prepare to replace strSettings
|
||||||
|
path_settings = xbmc.translatePath(
|
||||||
|
"special://profile/addon_data/metadata.tvshows.themoviedb.org/settings.xml")
|
||||||
|
settings_data = filetools.read(path_settings)
|
||||||
|
strSettings = ' '.join(settings_data.split()).replace("> <", "><")
|
||||||
|
strSettings = strSettings.replace("\"", "\'")
|
||||||
|
|
||||||
|
# update db
|
||||||
|
nun_records, records = xbmc_videolibrary.execute_sql_kodi(
|
||||||
|
'update path set strScraper="metadata.tvshows.themoviedb.org", strSettings="' + strSettings + '" where strPath like "' +
|
||||||
|
filetools.join(config.get_setting('videolibrarypath'), config.get_setting('folder_tvshows')) +
|
||||||
|
'%" and strScraper="metadata.tvdb.com"')
|
||||||
|
|
||||||
|
# scan new info
|
||||||
|
xbmc.executebuiltin('UpdateLibrary(video)')
|
||||||
|
xbmc.executebuiltin('CleanLibrary(video)')
|
||||||
|
while xbmc.getCondVisibility('Library.IsScanningVideo()'):
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
|
||||||
monitor = AddonMonitor()
|
monitor = AddonMonitor()
|
||||||
|
|
||||||
# mark as stopped all downloads (if we are here, probably kodi just started)
|
# mark as stopped all downloads (if we are here, probably kodi just started)
|
||||||
|
|||||||
@@ -401,6 +401,7 @@ def findvideos(item):
|
|||||||
json = item.url['links']
|
json = item.url['links']
|
||||||
else:
|
else:
|
||||||
json = item.url
|
json = item.url
|
||||||
|
|
||||||
for option in json:
|
for option in json:
|
||||||
extra = set_extra_values(item, option, item.path)
|
extra = set_extra_values(item, option, item.path)
|
||||||
title = item.fulltitle + (' - '+option['title'] if 'title' in option else '')
|
title = item.fulltitle + (' - '+option['title'] if 'title' in option else '')
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ class SearchWindow(xbmcgui.WindowXML):
|
|||||||
else:
|
else:
|
||||||
self.Focus(SEARCH)
|
self.Focus(SEARCH)
|
||||||
self.setFocusId(RESULTS)
|
self.setFocusId(RESULTS)
|
||||||
self.RESULTS.selectItem(self.epos)
|
self.RESULTS.selectItem(self.pos)
|
||||||
elif self.EPISODES.isVisible():
|
elif self.EPISODES.isVisible():
|
||||||
self.episodes = []
|
self.episodes = []
|
||||||
self.Focus(SEARCH)
|
self.Focus(SEARCH)
|
||||||
|
|||||||
+5
-5
@@ -107,11 +107,11 @@ def mainlist(item):
|
|||||||
# itemlist.append(new_item)
|
# itemlist.append(new_item)
|
||||||
|
|
||||||
#if list_canales['documentales']:
|
#if list_canales['documentales']:
|
||||||
thumbnail = get_thumb("documentary.png")
|
# thumbnail = get_thumb("documentary.png")
|
||||||
new_item = Item(channel=item.channel, action="novedades", extra="documentales", title=config.get_localized_string(60513),
|
# new_item = Item(channel=item.channel, action="novedades", extra="documentales", title=config.get_localized_string(60513),
|
||||||
thumbnail=thumbnail)
|
# thumbnail=thumbnail)
|
||||||
set_category_context(new_item)
|
# set_category_context(new_item)
|
||||||
itemlist.append(new_item)
|
# itemlist.append(new_item)
|
||||||
thumbnail = get_thumb("setting_0.png")
|
thumbnail = get_thumb("setting_0.png")
|
||||||
itemlist.append(Item(channel='shortcuts', action="SettingOnPosition", category=7, setting=1,
|
itemlist.append(Item(channel='shortcuts', action="SettingOnPosition", category=7, setting=1,
|
||||||
title=typo(config.get_localized_string(70285), 'bold color kod'), thumbnail=thumbnail))
|
title=typo(config.get_localized_string(70285), 'bold color kod'), thumbnail=thumbnail))
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from core import servertools
|
from core import servertools
|
||||||
from core.support import match, info
|
from core.support import match, info, server
|
||||||
from core.item import Item
|
from core.item import Item
|
||||||
from platformcode import config, logger
|
from platformcode import config, logger
|
||||||
|
|
||||||
@@ -27,10 +27,10 @@ def search(item, text):
|
|||||||
itemlist = []
|
itemlist = []
|
||||||
|
|
||||||
if "server" in item.args:
|
if "server" in item.args:
|
||||||
from core.support import server
|
|
||||||
itemlist = server(item, text)
|
itemlist = server(item, text)
|
||||||
elif "direct" in item.args:
|
elif "direct" in item.args:
|
||||||
itemlist.append(Item(channel=item.channel, action="play", url=text, server="directo", title=config.get_localized_string(60092)))
|
itemlist.append(Item(channel=item.channel, action="play", url=text, server="directo", title=config.get_localized_string(60092)))
|
||||||
|
itemlist = server(item, itemlist=itemlist)
|
||||||
else:
|
else:
|
||||||
data = match(text).data
|
data = match(text).data
|
||||||
itemlist = servertools.find_video_items(data=data)
|
itemlist = servertools.find_video_items(data=data)
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
"label": "@60651",
|
"label": "@60651",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"default": 0,
|
"default": 0,
|
||||||
|
"visible": false,
|
||||||
"lvalues": [
|
"lvalues": [
|
||||||
"TMDB",
|
"TMDB",
|
||||||
"None"
|
"None"
|
||||||
@@ -97,6 +98,7 @@
|
|||||||
"type": "list",
|
"type": "list",
|
||||||
"label": "@60652",
|
"label": "@60652",
|
||||||
"default": 0,
|
"default": 0,
|
||||||
|
"visible": false,
|
||||||
"lvalues": [
|
"lvalues": [
|
||||||
"TMDB",
|
"TMDB",
|
||||||
"TVDB"
|
"TVDB"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ PY3 = False
|
|||||||
if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
|
if sys.version_info[0] >= 3: PY3 = True; unicode = str; unichr = chr; long = int
|
||||||
|
|
||||||
import xbmc, os, traceback
|
import xbmc, os, traceback
|
||||||
|
from time import time
|
||||||
|
|
||||||
from core import filetools, scrapertools, videolibrarytools
|
from core import filetools, scrapertools, videolibrarytools
|
||||||
from core.support import typo, thumb
|
from core.support import typo, thumb
|
||||||
@@ -49,6 +50,9 @@ def list_movies(item, silent=False):
|
|||||||
local= True
|
local= True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# from core.support import dbg;dbg()
|
||||||
|
# for movie_path in movies_path:
|
||||||
|
# get_results(movie_path, root, 'movie', local)
|
||||||
with futures.ThreadPoolExecutor() as executor:
|
with futures.ThreadPoolExecutor() as executor:
|
||||||
itlist = [executor.submit(get_results, movie_path, root, 'movie', local) for movie_path in movies_path]
|
itlist = [executor.submit(get_results, movie_path, root, 'movie', local) for movie_path in movies_path]
|
||||||
for res in futures.as_completed(itlist):
|
for res in futures.as_completed(itlist):
|
||||||
@@ -67,6 +71,7 @@ def list_tvshows(item):
|
|||||||
lista = []
|
lista = []
|
||||||
|
|
||||||
root = videolibrarytools.TVSHOWS_PATH
|
root = videolibrarytools.TVSHOWS_PATH
|
||||||
|
start = time()
|
||||||
with futures.ThreadPoolExecutor() as executor:
|
with futures.ThreadPoolExecutor() as executor:
|
||||||
itlist = [executor.submit(get_results, filetools.join(root, folder, "tvshow.nfo"), root, 'tvshow') for folder in filetools.listdir(root)]
|
itlist = [executor.submit(get_results, filetools.join(root, folder, "tvshow.nfo"), root, 'tvshow') for folder in filetools.listdir(root)]
|
||||||
for res in futures.as_completed(itlist):
|
for res in futures.as_completed(itlist):
|
||||||
@@ -75,7 +80,7 @@ def list_tvshows(item):
|
|||||||
if item_tvshow.library_urls and len(item_tvshow.library_urls) > 0:
|
if item_tvshow.library_urls and len(item_tvshow.library_urls) > 0:
|
||||||
itemlist += [item_tvshow]
|
itemlist += [item_tvshow]
|
||||||
lista += [{'title':item_tvshow.contentTitle,'thumbnail':item_tvshow.thumbnail,'fanart':item_tvshow.fanart, 'active': value, 'nfo':item_tvshow.nfo}]
|
lista += [{'title':item_tvshow.contentTitle,'thumbnail':item_tvshow.thumbnail,'fanart':item_tvshow.fanart, 'active': value, 'nfo':item_tvshow.nfo}]
|
||||||
|
logger.debug('load list',time() - start)
|
||||||
if itemlist:
|
if itemlist:
|
||||||
itemlist = sorted(itemlist, key=lambda it: it.title.lower())
|
itemlist = sorted(itemlist, key=lambda it: it.title.lower())
|
||||||
|
|
||||||
@@ -88,13 +93,8 @@ def list_tvshows(item):
|
|||||||
|
|
||||||
def get_results(nfo_path, root, Type, local=False):
|
def get_results(nfo_path, root, Type, local=False):
|
||||||
value = 0
|
value = 0
|
||||||
if Type == 'movie': folder = "folder_movies"
|
|
||||||
else: folder = "folder_tvshows"
|
|
||||||
|
|
||||||
if filetools.exists(nfo_path):
|
if filetools.exists(nfo_path):
|
||||||
# We synchronize the episodes seen from the Kodi video library with that of KoD
|
|
||||||
from platformcode import xbmc_videolibrary
|
|
||||||
xbmc_videolibrary.mark_content_as_watched_on_kod(nfo_path)
|
|
||||||
head_nfo, item = videolibrarytools.read_nfo(nfo_path)
|
head_nfo, item = videolibrarytools.read_nfo(nfo_path)
|
||||||
|
|
||||||
# If you have not read the .nfo well, we will proceed to the next
|
# If you have not read the .nfo well, we will proceed to the next
|
||||||
@@ -107,6 +107,7 @@ def get_results(nfo_path, root, Type, local=False):
|
|||||||
|
|
||||||
# continue loading the elements of the video library
|
# continue loading the elements of the video library
|
||||||
if Type == 'movie':
|
if Type == 'movie':
|
||||||
|
folder = "folder_movies"
|
||||||
item.path = filetools.split(nfo_path)[0]
|
item.path = filetools.split(nfo_path)[0]
|
||||||
item.nfo = nfo_path
|
item.nfo = nfo_path
|
||||||
sep = '/' if '/' in nfo_path else '\\'
|
sep = '/' if '/' in nfo_path else '\\'
|
||||||
@@ -118,7 +119,7 @@ def get_results(nfo_path, root, Type, local=False):
|
|||||||
if not filetools.exists(filetools.join(item.path, filetools.basename(strm_path))) and not local: return Item(), 0
|
if not filetools.exists(filetools.join(item.path, filetools.basename(strm_path))) and not local: return Item(), 0
|
||||||
|
|
||||||
# Contextual menu: Mark as seen / not seen
|
# Contextual menu: Mark as seen / not seen
|
||||||
visto = item.library_playcounts.get(item.path.split(sep)[0], 0)
|
visto = item.library_playcounts.get(strm_path.strip('/').split('/')[0], 0)
|
||||||
item.infoLabels["playcount"] = visto
|
item.infoLabels["playcount"] = visto
|
||||||
if visto > 0:
|
if visto > 0:
|
||||||
seen_text = config.get_localized_string(60016)
|
seen_text = config.get_localized_string(60016)
|
||||||
@@ -136,7 +137,7 @@ def get_results(nfo_path, root, Type, local=False):
|
|||||||
item.context = [{"title": seen_text, "action": "mark_content_as_watched", "channel": "videolibrary", "playcount": counter},
|
item.context = [{"title": seen_text, "action": "mark_content_as_watched", "channel": "videolibrary", "playcount": counter},
|
||||||
{"title": delete_text, "action": "delete", "channel": "videolibrary", "multichannel": multichannel}]
|
{"title": delete_text, "action": "delete", "channel": "videolibrary", "multichannel": multichannel}]
|
||||||
else:
|
else:
|
||||||
# Sometimes it gives random errors, for not finding the .nfo. Probably timing issues
|
folder = "folder_tvshows"
|
||||||
try:
|
try:
|
||||||
item.title = item.contentTitle
|
item.title = item.contentTitle
|
||||||
item.path = filetools.split(nfo_path)[0]
|
item.path = filetools.split(nfo_path)[0]
|
||||||
@@ -558,6 +559,8 @@ def play(item):
|
|||||||
else:
|
else:
|
||||||
itemlist = [item.clone(url=item.url, server="local")]
|
itemlist = [item.clone(url=item.url, server="local")]
|
||||||
|
|
||||||
|
if not itemlist:
|
||||||
|
return []
|
||||||
# For direct links in list format
|
# For direct links in list format
|
||||||
if isinstance(itemlist[0], list):
|
if isinstance(itemlist[0], list):
|
||||||
item.video_urls = itemlist
|
item.video_urls = itemlist
|
||||||
@@ -907,7 +910,9 @@ def mark_season_as_watched(item):
|
|||||||
# logger.debug("item:\n" + item.tostring('\n'))
|
# logger.debug("item:\n" + item.tostring('\n'))
|
||||||
|
|
||||||
# Get dictionary of marked episodes
|
# Get dictionary of marked episodes
|
||||||
f = filetools.join(item.path, 'tvshow.nfo')
|
if not item.path: f = item.nfo
|
||||||
|
else: f = filetools.join(item.path, 'tvshow.nfo')
|
||||||
|
|
||||||
head_nfo, it = videolibrarytools.read_nfo(f)
|
head_nfo, it = videolibrarytools.read_nfo(f)
|
||||||
if not hasattr(it, 'library_playcounts'):
|
if not hasattr(it, 'library_playcounts'):
|
||||||
it.library_playcounts = {}
|
it.library_playcounts = {}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ rm tests/home/userdata/addon_data/plugin.video.kod/settings_channels/*.json
|
|||||||
rm tests/home/userdata/addon_data/plugin.video.kod/settings_servers/*.json
|
rm tests/home/userdata/addon_data/plugin.video.kod/settings_servers/*.json
|
||||||
rm tests/home/userdata/addon_data/plugin.video.kod/cookies.dat
|
rm tests/home/userdata/addon_data/plugin.video.kod/cookies.dat
|
||||||
rm tests/home/userdata/addon_data/plugin.video.kod/kod_db.sqlite
|
rm tests/home/userdata/addon_data/plugin.video.kod/kod_db.sqlite
|
||||||
python -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
pip install sakee
|
pip install sakee
|
||||||
pip install html-testRunner
|
pip install html-testRunner
|
||||||
pip install parameterized
|
pip install parameterized
|
||||||
|
|||||||
+18
-22
@@ -5,9 +5,16 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||||
|
<style>
|
||||||
|
textarea {
|
||||||
|
font-family: monospace;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<h2 class="text-capitalize">{{ title }}</h2>
|
<h2 class="text-capitalize">{{ title }}</h2>
|
||||||
@@ -55,7 +62,9 @@
|
|||||||
{%- if (test_case.stdout or test_case.err or test_case.err) and test_case.outcome != test_case.SKIP %}
|
{%- if (test_case.stdout or test_case.err or test_case.err) and test_case.outcome != test_case.SKIP %}
|
||||||
<tr style="display:none;">
|
<tr style="display:none;">
|
||||||
<td class="col-xs-9" colspan="3">
|
<td class="col-xs-9" colspan="3">
|
||||||
{%- if test_case.stdout %}<p style="white-space: pre-line;">{{ test_case.stdout|e }}</p>{% endif %}
|
<textarea rows="40" readonly>
|
||||||
|
{%- if test_case.stdout %}{{ test_case.stdout|e }}{% endif %}
|
||||||
|
</textarea>
|
||||||
{%- if test_case.err %}<p style="color:maroon;">{{ test_case.err[0].__name__ }}: {{ test_case.err[1] }}</p>{% endif %}
|
{%- if test_case.err %}<p style="color:maroon;">{{ test_case.err[0].__name__ }}: {{ test_case.err[1] }}</p>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -63,7 +72,9 @@
|
|||||||
{%- if (test_case.stdout or test_case.err or test_case.err) and test_case.outcome == test_case.SKIP %}
|
{%- if (test_case.stdout or test_case.err or test_case.err) and test_case.outcome == test_case.SKIP %}
|
||||||
<tr style="display:none;">
|
<tr style="display:none;">
|
||||||
<td class="col-xs-9" colspan="3">
|
<td class="col-xs-9" colspan="3">
|
||||||
{%- if test_case.stdout %}<p style="white-space: pre-line;">{{ test_case.stdout|e }}</p>{% endif %}
|
<textarea rows="40" readonly>
|
||||||
|
{%- if test_case.stdout %}{{ test_case.stdout|e }}{% endif %}
|
||||||
|
</textarea>
|
||||||
{%- if test_case.err %}<p style="color:maroon;">{{ test_case.err }}</p>{% endif %}
|
{%- if test_case.err %}<p style="color:maroon;">{{ test_case.err }}</p>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -113,8 +124,10 @@
|
|||||||
{%- if subtest.err or subtest.err %}
|
{%- if subtest.err or subtest.err %}
|
||||||
<tr style="display:none;">
|
<tr style="display:none;">
|
||||||
<td class="col-xs-9" colspan="3">
|
<td class="col-xs-9" colspan="3">
|
||||||
{%- if subtest.err %}<p style="color:maroon;">{{ subtest.err[0].__name__ }}: {{ subtest.err[1] }}</p>{% endif %}
|
<textarea rows="40" readonly>
|
||||||
{%- if subtest.err %}<p style="color:maroon;">{{ subtest.test_exception_info }}</p>{% endif %}
|
{%- if subtest.err %}{{ subtest.err[0].__name__ }}: {{ subtest.err[1] }}{% endif %}
|
||||||
|
</textarea>
|
||||||
|
{%- if subtest.err %}<p style="color:maroon;">{{ subtest.test_exception_info }}</p>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
@@ -126,11 +139,6 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
<tr>
|
|
||||||
<td colspan="3">
|
|
||||||
Total: {{ summaries[test_case_name].total }}, Pass: {{ summaries[test_case_name].success }}{% if summaries[test_case_name].failure %}, Fail: {{ summaries[test_case_name].failure }}{% endif %}{% if summaries[test_case_name].error %}, Error: {{ summaries[test_case_name].error }}{% endif %}{% if summaries[test_case_name].skip %}, Skip: {{ summaries[test_case_name].skip }}{% endif %} -- Duration: {{ summaries[test_case_name].duration }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,30 +179,18 @@
|
|||||||
}
|
}
|
||||||
$('#showPassed').on('click', function(){
|
$('#showPassed').on('click', function(){
|
||||||
$(".success").toggle(this.checked);
|
$(".success").toggle(this.checked);
|
||||||
if (this.checked == false) {
|
|
||||||
$(".success").next('tr').toggle(this.checked);
|
|
||||||
}
|
|
||||||
hideOrShow()
|
hideOrShow()
|
||||||
});
|
});
|
||||||
$('#showFailed').on('click', function(){
|
$('#showFailed').on('click', function(){
|
||||||
$(".danger").toggle(this.checked);
|
$(".danger").toggle(this.checked);
|
||||||
if (this.checked == false) {
|
|
||||||
$(".danger").next('tr').toggle(this.checked);
|
|
||||||
}
|
|
||||||
hideOrShow()
|
hideOrShow()
|
||||||
});
|
});
|
||||||
$('#showErrors').on('click', function(){
|
$('#showErrors').on('click', function(){
|
||||||
$(".warning").toggle(this.checked);
|
$(".warning").toggle(this.checked);
|
||||||
if (this.checked == false) {
|
|
||||||
$(".warning").next('tr').toggle(this.checked);
|
|
||||||
}
|
|
||||||
hideOrShow()
|
hideOrShow()
|
||||||
});
|
});
|
||||||
$('#showSkipped').on('click', function(){
|
$('#showSkipped').on('click', function(){
|
||||||
$(".info").toggle(this.checked);
|
$(".info").toggle(this.checked);
|
||||||
if (this.checked == false) {
|
|
||||||
$(".info").next('tr').toggle(this.checked);
|
|
||||||
}
|
|
||||||
hideOrShow()
|
hideOrShow()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-12
@@ -14,19 +14,18 @@ import unittest
|
|||||||
import xbmc
|
import xbmc
|
||||||
|
|
||||||
if 'KOD_TST_CH' not in os.environ:
|
if 'KOD_TST_CH' not in os.environ:
|
||||||
|
from sakee import addoninfo
|
||||||
# custom paths
|
# custom paths
|
||||||
def add_on_info(*args, **kwargs):
|
def add_on_info(*args, **kwargs):
|
||||||
return xbmc.AddonData(
|
return addoninfo.AddonData(
|
||||||
kodi_home_path=os.path.join(os.getcwd(), 'tests', 'home'),
|
kodi_home_path=os.path.join(os.getcwd(), 'tests', 'home'),
|
||||||
add_on_id='plugin.video.kod',
|
add_on_id='plugin.video.kod',
|
||||||
add_on_path=os.getcwd(),
|
add_on_path=os.getcwd(),
|
||||||
kodi_profile_path=os.path.join(os.getcwd(), 'tests', 'home', 'userdata')
|
kodi_profile_path=os.path.join(os.getcwd(), 'tests', 'home', 'userdata')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# override
|
# override
|
||||||
xbmc.get_add_on_info_from_calling_script = add_on_info
|
addoninfo.get_add_on_info_from_calling_script = add_on_info
|
||||||
|
|
||||||
|
|
||||||
# functions that on kodi 19 moved to xbmcvfs
|
# functions that on kodi 19 moved to xbmcvfs
|
||||||
try:
|
try:
|
||||||
@@ -49,10 +48,14 @@ sys.path.insert(0, librerias)
|
|||||||
from core.support import typo
|
from core.support import typo
|
||||||
from core.item import Item
|
from core.item import Item
|
||||||
from core.httptools import downloadpage
|
from core.httptools import downloadpage
|
||||||
from core import servertools
|
from core import servertools, httptools
|
||||||
import channelselector
|
import channelselector
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
httptools.HTTPTOOLS_DEFAULT_DOWNLOAD_TIMEOUT = 60
|
||||||
|
|
||||||
|
outDir = os.path.join(os.getcwd(), 'reports')
|
||||||
validUrlRegex = re.compile(
|
validUrlRegex = re.compile(
|
||||||
r'^(?:http|ftp)s?://' # http:// or https://
|
r'^(?:http|ftp)s?://' # http:// or https://
|
||||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
|
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
|
||||||
@@ -144,7 +147,7 @@ channels = []
|
|||||||
|
|
||||||
channel_list = channelselector.filterchannels("all") if 'KOD_TST_CH' not in os.environ else [Item(channel=os.environ['KOD_TST_CH'], action="mainlist")]
|
channel_list = channelselector.filterchannels("all") if 'KOD_TST_CH' not in os.environ else [Item(channel=os.environ['KOD_TST_CH'], action="mainlist")]
|
||||||
logger.info([c.channel for c in channel_list])
|
logger.info([c.channel for c in channel_list])
|
||||||
ret = []
|
results = []
|
||||||
|
|
||||||
logger.record = True
|
logger.record = True
|
||||||
for chItem in channel_list:
|
for chItem in channel_list:
|
||||||
@@ -179,8 +182,6 @@ for chItem in channel_list:
|
|||||||
|
|
||||||
itemlist = getattr(module, it.action)(it)
|
itemlist = getattr(module, it.action)(it)
|
||||||
menuItemlist[it.title] = itemlist
|
menuItemlist[it.title] = itemlist
|
||||||
logMenu[it.title] = logger.recordedLog
|
|
||||||
logger.recordedLog = ''
|
|
||||||
|
|
||||||
# some sites might have no link inside, but if all results are without servers, there's something wrong
|
# some sites might have no link inside, but if all results are without servers, there's something wrong
|
||||||
for resIt in itemlist:
|
for resIt in itemlist:
|
||||||
@@ -206,9 +207,15 @@ for chItem in channel_list:
|
|||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
logMenu[it.title] = logger.recordedLog
|
|
||||||
logger.recordedLog = ''
|
|
||||||
|
|
||||||
|
logMenu[it.title] = logger.recordedLog
|
||||||
|
logger.recordedLog = ''
|
||||||
|
|
||||||
|
# results.append(
|
||||||
|
# {'ch': ch, 'hasChannelConfig': hasChannelConfig, 'mainlist': [it.title for it in mainlist],
|
||||||
|
# 'menuItemlist': {k: [it.tojson() if type(it) == Item else it for it in menuItemlist[k]] for k in menuItemlist.keys()},
|
||||||
|
# 'serversFound': {k: [it.tojson() if type(it) == Item else it for it in menuItemlist[k]] for k in menuItemlist.keys()},
|
||||||
|
# 'module': str(module), 'logMenu': logMenu, 'error': error})
|
||||||
channels.append(
|
channels.append(
|
||||||
{'ch': ch, 'hasChannelConfig': hasChannelConfig, 'mainlist': mainlist, 'menuItemlist': menuItemlist,
|
{'ch': ch, 'hasChannelConfig': hasChannelConfig, 'mainlist': mainlist, 'menuItemlist': menuItemlist,
|
||||||
'serversFound': serversFound, 'module': module, 'logMenu': logMenu, 'error': error})
|
'serversFound': serversFound, 'module': module, 'logMenu': logMenu, 'error': error})
|
||||||
@@ -217,7 +224,10 @@ logger.record = False
|
|||||||
|
|
||||||
from specials import news
|
from specials import news
|
||||||
dictNewsChannels, any_active = news.get_channels_list()
|
dictNewsChannels, any_active = news.get_channels_list()
|
||||||
print(channels)
|
# if not os.path.isdir(outDir):
|
||||||
|
# os.mkdir(outDir)
|
||||||
|
# json.dump(results, open(os.path.join(outDir, 'result.json'), 'w'))
|
||||||
|
|
||||||
# only 1 server item for single server
|
# only 1 server item for single server
|
||||||
serverNames = []
|
serverNames = []
|
||||||
serversFinal = []
|
serversFinal = []
|
||||||
@@ -350,6 +360,6 @@ if __name__ == '__main__':
|
|||||||
unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(report_name='report', add_timestamp=False, combine_reports=True,
|
unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(report_name='report', add_timestamp=False, combine_reports=True,
|
||||||
report_title='KoD Test Suite', template=os.path.join(config.get_runtime_path(), 'tests', 'template.html')), exit=False)
|
report_title='KoD Test Suite', template=os.path.join(config.get_runtime_path(), 'tests', 'template.html')), exit=False)
|
||||||
import webbrowser
|
import webbrowser
|
||||||
webbrowser.open(os.path.join(config.get_runtime_path(), 'reports', 'report.html'))
|
webbrowser.open(os.path.join(outDir, 'report.html'))
|
||||||
else:
|
else:
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
+15
-2
@@ -58,9 +58,20 @@ if __name__ == '__main__':
|
|||||||
# redirect
|
# redirect
|
||||||
elif str(rslt['code']).startswith('3'):
|
elif str(rslt['code']).startswith('3'):
|
||||||
# result[chann] = str(rslt['code']) +' - '+ rslt['redirect'][:-1]
|
# result[chann] = str(rslt['code']) +' - '+ rslt['redirect'][:-1]
|
||||||
if rslt['redirect'].endswith('/'):
|
|
||||||
rslt['redirect'] = rslt['redirect'][:-1]
|
|
||||||
result[chann] = rslt['redirect']
|
result[chann] = rslt['redirect']
|
||||||
|
# cloudflare...
|
||||||
|
elif rslt['code'] in [429, 503, 403]:
|
||||||
|
from lib import proxytranslate
|
||||||
|
import re
|
||||||
|
|
||||||
|
print('Cloudflare riconosciuto')
|
||||||
|
try:
|
||||||
|
page_data = proxytranslate.process_request_proxy(host).get('data', '')
|
||||||
|
result[chann] = re.search('<base href="([^"]+)', page_data).group(1)
|
||||||
|
rslt['code_new'] = 200
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
# non-existent site
|
# non-existent site
|
||||||
elif rslt['code'] == -2:
|
elif rslt['code'] == -2:
|
||||||
print('Host Sconosciuto - '+ str(rslt['code']) +' - '+ host)
|
print('Host Sconosciuto - '+ str(rslt['code']) +' - '+ host)
|
||||||
@@ -72,6 +83,8 @@ if __name__ == '__main__':
|
|||||||
print('Errore Sconosciuto - '+str(rslt['code']) +' - '+ host)
|
print('Errore Sconosciuto - '+str(rslt['code']) +' - '+ host)
|
||||||
|
|
||||||
print("check #### FINE #### rslt :%s " % (rslt))
|
print("check #### FINE #### rslt :%s " % (rslt))
|
||||||
|
if result[chann].endswith('/'):
|
||||||
|
result[chann] = result[chann][:-1]
|
||||||
|
|
||||||
result = {'findhost': data['findhost'], 'direct': result}
|
result = {'findhost': data['findhost'], 'direct': result}
|
||||||
# I write the updated file
|
# I write the updated file
|
||||||
|
|||||||
Reference in New Issue
Block a user