- 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:
mac12m99
2021-02-13 16:37:02 +01:00
parent 0ebc744115
commit 748fad7431
64 changed files with 2203 additions and 712 deletions
+33
View File
@@ -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'
+5 -3
View File
@@ -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
+7 -5
View File
@@ -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
View File
@@ -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"
} }
} }
+3 -2
View File
@@ -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>.+?)\])?<'
+3 -3
View File
@@ -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
View File
@@ -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()
+1 -1
View File
@@ -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
+3 -1
View File
@@ -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>.+?)(?: &#8211; 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>.+?)(?: &#8211; 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
+12
View File
@@ -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": []
}
+186
View File
@@ -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)
+1 -1
View File
@@ -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',))
+2 -1
View File
@@ -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 &raquo;</a>' patronNext=r'a class="next page-numbers" href="?([^>"]+)">Avanti &raquo;</a>'
return locals() return locals()
+1 -1
View File
@@ -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
View File
@@ -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()
+10 -9
View File
@@ -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 []
+4 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+3 -2
View File
@@ -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="([^"]+)">&raquo' patronNext = r'<a href="([^"]+)">(?:&raquo|»)'
typeContentDict = {'': 'music'} typeContentDict = {'': 'music'}
return locals() return locals()
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+3 -3
View File
@@ -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>[^"]+)"(?:|.+?)?>(?:&nbsp;&nbsp;)?[ ]?(?P<title>.*?)[ ]?(?P<quality>HD)?[ ]?(?P<year>\d+)?(?: | HD | Streaming | MD(?: iSTANCE)? )?</a>' patron = r'<tr><td><a href="(?P<url>[^"]+)"(?:|.+?)?>(?:&nbsp;&nbsp;)?[ ]?(?P<title>.*?)[ ]?(?P<quality>HD)?[ ]?(?P<year>\d+)?(?: | HD | Streaming | MD(?: iSTANCE)? )?</a>'
+23 -18
View File
@@ -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
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+4
View File
@@ -0,0 +1,4 @@
# __init__.py
# Version of the fuzzy-match package
__version__ = "0.0.1"
+181
View File
@@ -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
+78
View File
@@ -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
View File
@@ -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=(.*?)&amp;usg=[A-Za-z0-9_-]+', '\\1', data) data = re.sub('https://translate\.googleusercontent\.com/.*?u=(.*?)&amp;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('&amp;', '&').replace('https://translate.google.com/website?sl=' + SL + '&tl=' + TL + '&u=', '')
return {'url': url.strip(), 'result': result, 'data': data.replace('&amp;', '&')} 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)
+601
View File
@@ -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
View File
@@ -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()
+15 -4
View File
@@ -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
View File
@@ -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)
+77 -45
View File
@@ -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):
BIN
View File
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.
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -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 '')
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+2
View File
@@ -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"
+14 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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