1058 lines
41 KiB
Python
1058 lines
41 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Code de depart par AnthonyBloomer
|
|
# Modif pour vStream
|
|
# https://github.com/Kodi-vStream/venom-xbmc-addons/
|
|
|
|
import re
|
|
import json
|
|
|
|
import xbmcvfs
|
|
import string
|
|
import unicodedata
|
|
import webbrowser
|
|
|
|
from resources.lib.util import QuotePlus
|
|
from resources.lib.comaddon import addon, dialog, VSlog, VSPath, isMatrix
|
|
from resources.lib.handler.requestHandler import cRequestHandler
|
|
|
|
try:
|
|
import urllib2
|
|
except ImportError:
|
|
import urllib.request as urllib2
|
|
|
|
try:
|
|
from sqlite3 import dbapi2 as sqlite
|
|
VSlog('SQLITE 3 as DB engine for tmdb')
|
|
except:
|
|
from pysqlite2 import dbapi2 as sqlite
|
|
VSlog('SQLITE 2 as DB engine for tmdb')
|
|
|
|
class cTMDb:
|
|
|
|
# https://developers.themoviedb.org/3/genres/get-movie-list
|
|
# https://developers.themoviedb.org/3/genres/get-tv-list
|
|
TMDB_GENRES = {
|
|
12: 'Aventure',
|
|
14: 'Fantastique',
|
|
16: 'Animation',
|
|
18: 'Drame',
|
|
27: 'Horreur',
|
|
28: 'Action',
|
|
35: 'Comédie',
|
|
36: 'Histoire',
|
|
37: 'Western',
|
|
53: 'Thriller',
|
|
80: 'Crime',
|
|
99: 'Documentaire',
|
|
878: 'Science-Fiction',
|
|
9648: 'Mystère',
|
|
10402: 'Musique',
|
|
10749: 'Romance',
|
|
10751: 'Familial',
|
|
10752: 'Guerre',
|
|
10759: 'Action & Aventure',
|
|
10762: 'Kids',
|
|
10763: 'News',
|
|
10764: 'Realité',
|
|
10765: 'Science-Fiction & Fantastique',
|
|
10766: 'Feuilleton',
|
|
10767: 'Talk',
|
|
10768: 'Guerre & Politique',
|
|
10769: 'Etranger',
|
|
10770: 'Téléfilm'
|
|
}
|
|
|
|
URL = 'https://api.themoviedb.org/3/'
|
|
URL_TRAILER = 'plugin://plugin.video.youtube/play/?video_id=%s' # ancien : 'plugin://plugin.video.youtube/?action=play_video&videoid=%s'
|
|
CACHE = 'special://home/userdata/addon_data/plugin.video.vstream/video_cache.db'
|
|
|
|
# important seul xbmcvfs peux lire le special
|
|
if not isMatrix:
|
|
REALCACHE = VSPath(CACHE).decode('utf-8')
|
|
else:
|
|
REALCACHE = VSPath(CACHE)
|
|
|
|
|
|
def __init__(self, api_key='', debug=False, lang='fr'):
|
|
|
|
self.ADDON = addon()
|
|
|
|
self.api_key = self.ADDON.getSetting('api_tmdb')
|
|
self.debug = debug
|
|
self.lang = lang
|
|
self.poster = 'https://image.tmdb.org/t/p/%s' % self.ADDON.getSetting('poster_tmdb')
|
|
self.fanart = 'https://image.tmdb.org/t/p/%s' % self.ADDON.getSetting('backdrop_tmdb')
|
|
|
|
try:
|
|
if not xbmcvfs.exists(self.CACHE):
|
|
# f = open(self.cache, 'w')
|
|
# f.close()
|
|
self.db = sqlite.connect(self.REALCACHE)
|
|
self.db.row_factory = sqlite.Row
|
|
self.dbcur = self.db.cursor()
|
|
self.__createdb()
|
|
return
|
|
except:
|
|
VSlog('Error: Unable to write on %s' % self.REALCACHE)
|
|
pass
|
|
|
|
try:
|
|
self.db = sqlite.connect(self.REALCACHE)
|
|
self.db.row_factory = sqlite.Row
|
|
self.dbcur = self.db.cursor()
|
|
except:
|
|
VSlog('Error: Unable to connect to %s' % self.REALCACHE)
|
|
pass
|
|
|
|
def __createdb(self):
|
|
|
|
sql_create = "CREATE TABLE IF NOT EXISTS movie ("\
|
|
"imdb_id TEXT, "\
|
|
"tmdb_id TEXT, "\
|
|
"title TEXT, "\
|
|
"year INTEGER,"\
|
|
"director TEXT, "\
|
|
"writer TEXT, "\
|
|
"tagline TEXT, "\
|
|
"credits TEXT,"\
|
|
"vote_average FLOAT, "\
|
|
"vote_count TEXT, "\
|
|
"runtime TEXT, "\
|
|
"overview TEXT,"\
|
|
"mpaa TEXT, "\
|
|
"premiered TEXT, "\
|
|
"genre TEXT, "\
|
|
"studio TEXT,"\
|
|
"status TEXT,"\
|
|
"poster_path TEXT, "\
|
|
"trailer TEXT, "\
|
|
"backdrop_path TEXT,"\
|
|
"playcount INTEGER,"\
|
|
"UNIQUE(imdb_id, tmdb_id, title, year)"\
|
|
");"
|
|
try:
|
|
self.dbcur.execute(sql_create)
|
|
except:
|
|
VSlog('Error: Cannot create table movie')
|
|
|
|
sql_create = "CREATE TABLE IF NOT EXISTS tvshow ("\
|
|
"imdb_id TEXT, "\
|
|
"tmdb_id TEXT, "\
|
|
"title TEXT, "\
|
|
"year INTEGER,"\
|
|
"director TEXT, "\
|
|
"writer TEXT, "\
|
|
"credits TEXT,"\
|
|
"vote_average FLOAT, "\
|
|
"vote_count TEXT, "\
|
|
"runtime TEXT, "\
|
|
"overview TEXT,"\
|
|
"mpaa TEXT, "\
|
|
"premiered TEXT, "\
|
|
"genre TEXT, "\
|
|
"studio TEXT,"\
|
|
"status TEXT,"\
|
|
"poster_path TEXT,"\
|
|
"trailer TEXT, "\
|
|
"backdrop_path TEXT,"\
|
|
"playcount INTEGER,"\
|
|
"UNIQUE(imdb_id, tmdb_id, title)"\
|
|
");"
|
|
|
|
self.dbcur.execute(sql_create)
|
|
|
|
sql_create = "CREATE TABLE IF NOT EXISTS season ("\
|
|
"imdb_id TEXT, "\
|
|
"tmdb_id TEXT, " \
|
|
"season INTEGER, "\
|
|
"year INTEGER,"\
|
|
"premiered TEXT, "\
|
|
"poster_path TEXT,"\
|
|
"playcount INTEGER,"\
|
|
"UNIQUE(imdb_id, tmdb_id, season)"\
|
|
");"
|
|
|
|
self.dbcur.execute(sql_create)
|
|
|
|
sql_create = "CREATE TABLE IF NOT EXISTS episode ("\
|
|
"imdb_id TEXT, "\
|
|
"tmdb_id TEXT, "\
|
|
"episode_id TEXT, "\
|
|
"season INTEGER, "\
|
|
"episode INTEGER, "\
|
|
"title TEXT, "\
|
|
"director TEXT, "\
|
|
"writer TEXT, "\
|
|
"overview TEXT, "\
|
|
"vote_average FLOAT, "\
|
|
"premiered TEXT, "\
|
|
"poster_path TEXT, "\
|
|
"playcount INTEGER, "\
|
|
"UNIQUE(imdb_id, tmdb_id, episode_id, title)"\
|
|
");"
|
|
|
|
self.dbcur.execute(sql_create)
|
|
VSlog('table movie creee')
|
|
|
|
def __del__(self):
|
|
""" Cleanup db when object destroyed """
|
|
try:
|
|
self.dbcur.close()
|
|
self.db.close()
|
|
except:
|
|
pass
|
|
|
|
def getToken(self):
|
|
|
|
result = self._call('authentication/token/new', '')
|
|
|
|
total = len(result)
|
|
|
|
if (total > 0):
|
|
url = 'https://www.themoviedb.org/authenticate/'
|
|
try:
|
|
#Si possible on ouvre la page automatiquement dans un navigateur internet.
|
|
webbrowser.open(url + result['request_token'])
|
|
except:
|
|
pass
|
|
|
|
sText = (self.ADDON.VSlang(30421)) % (url, result['request_token'])
|
|
|
|
DIALOG = dialog()
|
|
if not DIALOG.VSyesno(sText):
|
|
return False
|
|
|
|
result = self._call('authentication/session/new', 'request_token=' + result['request_token'])
|
|
|
|
if 'success' in result and result['success']:
|
|
self.ADDON.setSetting('tmdb_session', str(result['session_id']))
|
|
DIALOG.VSinfo(self.ADDON.VSlang(30000))
|
|
return
|
|
else:
|
|
DIALOG.VSerror('Erreur' + self.ADDON.VSlang(30000))
|
|
return
|
|
|
|
# xbmc.executebuiltin('Container.Refresh')
|
|
return
|
|
return
|
|
|
|
# cherche dans les films ou serie l'id par le nom, return ID ou FALSE
|
|
def get_idbyname(self, name, year='', mediaType='movie', page=1):
|
|
|
|
if year:
|
|
term = QuotePlus(name) + '&year=' + year
|
|
else:
|
|
term = QuotePlus(name)
|
|
|
|
if mediaType == "tv":
|
|
term = term.split('aison')[0].replace('+', ' ')
|
|
|
|
meta = self._call('search/' + str(mediaType), 'query=' + term + '&page=' + str(page))
|
|
|
|
# si pas de résultat avec l'année, on teste sans l'année
|
|
if 'total_results' in meta and meta['total_results'] == 0 and year:
|
|
meta = self.search_movie_name(name, '')
|
|
|
|
# cherche 1 seul resultat
|
|
if 'total_results' in meta and meta['total_results'] != 0:
|
|
if meta['total_results'] > 1:
|
|
qua = []
|
|
url = []
|
|
for aEntry in meta['results']:
|
|
url.append(aEntry["id"])
|
|
qua.append(aEntry['title'])
|
|
|
|
#Affichage du tableau
|
|
tmdb_id = dialog().VSselectqual(qua, url)
|
|
|
|
else:
|
|
tmdb_id = meta['results'][0]['id']
|
|
return tmdb_id
|
|
|
|
else:
|
|
return False
|
|
|
|
return False
|
|
|
|
# Search for movies by title.
|
|
def search_movie_name(self, name, year='', page=1):
|
|
|
|
name = re.sub(" +", " ", name) # nettoyage du titre
|
|
|
|
if year:
|
|
term = QuotePlus(name) + '&year=' + year
|
|
else:
|
|
term = QuotePlus(name)
|
|
|
|
meta = self._call('search/movie', 'query=' + term + '&page=' + str(page))
|
|
|
|
if 'errors' not in meta and 'status_code' not in meta:
|
|
|
|
# si pas de résultat avec l'année, on teste sans l'année
|
|
if 'total_results' in meta and meta['total_results'] == 0 and year:
|
|
meta = self.search_movie_name(name, '')
|
|
|
|
# cherche 1 seul resultat
|
|
if 'total_results' in meta and meta['total_results'] != 0:
|
|
|
|
movie = ''
|
|
|
|
# s'il n'y en a qu'un, c'est le bon
|
|
if meta['total_results'] == 1:
|
|
movie = meta['results'][0]
|
|
|
|
else:
|
|
# premiere boucle, recherche la correspondance parfaite sur le nom
|
|
for searchMovie in meta['results']:
|
|
if searchMovie['genre_ids'] and 99 not in searchMovie['genre_ids']:
|
|
if self._clean_title(searchMovie['title']) == self._clean_title(name):
|
|
movie = searchMovie
|
|
break
|
|
# sinon, hors documentaire et année proche
|
|
if not movie:
|
|
for searchMovie in meta['results']:
|
|
if searchMovie['genre_ids'] and 99 not in searchMovie['genre_ids']:
|
|
|
|
# controle supplémentaire sur l'année meme si déjà dans la requete
|
|
if year:
|
|
if 'release_date' in searchMovie and searchMovie['release_date']:
|
|
release_date = searchMovie['release_date']
|
|
yy = release_date[:4]
|
|
if int(year)-int(yy) > 1 :
|
|
continue # plus de deux ans d'écart, c'est pas bon
|
|
movie = searchMovie
|
|
break
|
|
|
|
# Rien d'interessant, on prend le premier
|
|
if not movie:
|
|
movie = meta['results'][0]
|
|
|
|
# recherche de toutes les infos
|
|
tmdb_id = movie['id']
|
|
meta = self.search_movie_id(tmdb_id)
|
|
else:
|
|
meta = {}
|
|
|
|
return meta
|
|
|
|
# Search for collections by title.
|
|
def search_collection_name(self, name):
|
|
|
|
name = re.sub(" +", " ", name) # nettoyage du titre
|
|
|
|
term = QuotePlus(name)
|
|
|
|
meta = self._call('search/collection', 'query=' + term)
|
|
|
|
if 'errors' not in meta and 'status_code' not in meta:
|
|
|
|
# cherche 1 seul resultat
|
|
if 'total_results' in meta and meta['total_results'] != 0:
|
|
|
|
collection = ''
|
|
|
|
# s'il n'y en a qu'un, c'est le bon
|
|
if meta['total_results'] == 1:
|
|
collection = meta['results'][0]
|
|
|
|
else:
|
|
# premiere boucle, recherche la correspondance parfaite sur le nom
|
|
for searchCollec in meta['results']:
|
|
cleanTitleTMDB = self._clean_title(searchCollec['name'])
|
|
cleanTitleSearch = self._clean_title(name)
|
|
if not cleanTitleSearch.endswith('saga'):
|
|
cleanTitleSearch += 'saga'
|
|
if cleanTitleTMDB == cleanTitleSearch:
|
|
collection = searchCollec
|
|
break
|
|
elif (cleanTitleSearch + 'saga')== cleanTitleTMDB:
|
|
collection = searchCollec
|
|
break
|
|
# sinon, le premier qui n'est pas du genre animation
|
|
if not collection:
|
|
for searchCollec in meta['results']:
|
|
if 'animation' not in searchCollec['name']:
|
|
collection = searchCollec
|
|
break
|
|
|
|
# Rien d'interessant, on prend le premier
|
|
if not collection:
|
|
collection = meta['results'][0]
|
|
|
|
meta = collection
|
|
tmdb_id = collection['id']
|
|
meta['tmdb_id'] = tmdb_id
|
|
|
|
# recherche de toutes les infos
|
|
meta = self.search_collection_id(tmdb_id)
|
|
else:
|
|
meta = {}
|
|
|
|
return meta
|
|
|
|
# Search for TV shows by title.
|
|
def search_tvshow_name(self, name, year='', page=1, genre=''):
|
|
|
|
if year:
|
|
term = QuotePlus(name) + '&year=' + year
|
|
else:
|
|
term = QuotePlus(name)
|
|
|
|
meta = self._call('search/tv', 'query=' + term + '&page=' + str(page))
|
|
if 'errors' not in meta and 'status_code' not in meta:
|
|
|
|
# si pas de résultat avec l'année, on teste sans l'année
|
|
if 'total_results' in meta and meta['total_results'] == 0 and year:
|
|
meta = self.search_tvshow_name(name, '')
|
|
|
|
# cherche 1 seul resultat
|
|
if 'total_results' in meta and meta['total_results'] != 0:
|
|
movie = ''
|
|
|
|
# s'il n'y en a qu'un, c'est le bon
|
|
if meta['total_results'] == 1:
|
|
movie = meta['results'][0]
|
|
|
|
else:
|
|
# premiere boucle, recherche la correspondance parfaite sur le nom
|
|
for searchMovie in meta['results']:
|
|
if genre == '' or genre in searchMovie['genre_ids']:
|
|
movieName = searchMovie['name']
|
|
if self._clean_title(movieName) == self._clean_title(name):
|
|
movie = searchMovie
|
|
break
|
|
# sinon, hors documentaire et année proche
|
|
if not movie:
|
|
for searchMovie in meta['results']:
|
|
if genre and genre in searchMovie['genre_ids']:
|
|
|
|
# controle supplémentaire sur l'année meme si déjà dans la requete
|
|
if year:
|
|
if 'release_date' in searchMovie and searchMovie['release_date']:
|
|
release_date = searchMovie['release_date']
|
|
yy = release_date[:4]
|
|
if int(year)-int(yy) > 1 :
|
|
continue # plus de deux ans d'écart, c'est pas bon
|
|
movie = searchMovie
|
|
break
|
|
|
|
# Rien d'interessant, on prend le premier
|
|
if not movie:
|
|
movie = meta['results'][0]
|
|
|
|
# recherche de toutes les infos
|
|
tmdb_id = movie['id']
|
|
meta = self.search_tvshow_id(tmdb_id)
|
|
else:
|
|
meta = {}
|
|
|
|
return meta
|
|
|
|
# Search for person by name.
|
|
def search_person_name(self, name):
|
|
name = re.sub(" +", " ", name) # nettoyage du titre
|
|
term = QuotePlus(name)
|
|
|
|
meta = self._call('search/person', 'query=' + term)
|
|
|
|
# si pas d'erreur
|
|
if 'errors' not in meta and 'status_code' not in meta:
|
|
|
|
# on prend le premier resultat
|
|
if 'total_results' in meta and meta['total_results'] != 0:
|
|
meta = meta['results'][0]
|
|
|
|
# recherche de toutes les infos
|
|
person_id = meta['id']
|
|
meta = self.search_person_id(person_id)
|
|
else:
|
|
meta = {}
|
|
|
|
return meta
|
|
|
|
# Get the basic movie information for a specific movie id.
|
|
def search_movie_id(self, movie_id, append_to_response='append_to_response=trailers,credits'):
|
|
result = self._call('movie/' + str(movie_id), append_to_response)
|
|
result['tmdb_id'] = movie_id
|
|
return result # obj(**self._call('movie/' + str(movie_id), append_to_response))
|
|
|
|
# Get the primary information about a TV series by id.
|
|
def search_tvshow_id(self, show_id, append_to_response='append_to_response=external_ids,videos,credits'):
|
|
result = self._call('tv/' + str(show_id), append_to_response)
|
|
result['tmdb_id'] = show_id
|
|
return result
|
|
|
|
# Get the basic informations for a specific collection id.
|
|
def search_collection_id(self, collection_id):
|
|
result = self._call('collection/' + str(collection_id))
|
|
result['tmdb_id'] = collection_id
|
|
return result
|
|
|
|
# Get the basic person informations for a specific person id.
|
|
def search_person_id(self, person_id):
|
|
result = self._call('person/' + str(person_id))
|
|
result['tmdb_id'] = person_id
|
|
return result
|
|
|
|
# Get the informations for a specific network.
|
|
def search_network_id(self, network_id):
|
|
result = self._call('network/%s/images' % str(network_id))
|
|
if 'status_code' not in result and 'logos' in result:
|
|
network = result['logos'][0]
|
|
vote = -1
|
|
|
|
# On prend le logo qui a la meilleure note
|
|
for logo in result['logos']:
|
|
logoVote = float(logo['vote_average'])
|
|
if logoVote>vote:
|
|
network = logo
|
|
vote = logoVote
|
|
network['tmdb_id'] = network_id
|
|
network.pop('vote_average')
|
|
return network
|
|
return {}
|
|
|
|
def _format(self, meta, name):
|
|
_meta = {}
|
|
_meta['imdb_id'] = ''
|
|
_meta['tmdb_id'] = ''
|
|
_meta['tvdb_id'] = ''
|
|
_meta['title'] = name
|
|
_meta['media_type'] = ''
|
|
_meta['rating'] = 0
|
|
_meta['votes'] = 0
|
|
_meta['duration'] = 0
|
|
_meta['plot'] = ''
|
|
_meta['mpaa'] = ''
|
|
_meta['premiered'] = ''
|
|
_meta['year'] = ''
|
|
_meta['trailer'] = ''
|
|
_meta['tagline'] = ''
|
|
_meta['genre'] = ''
|
|
_meta['studio'] = ''
|
|
_meta['status'] = ''
|
|
_meta['credits'] = ''
|
|
_meta['cast'] = []
|
|
_meta['director'] = ''
|
|
_meta['writer'] = ''
|
|
_meta['poster_path'] = ''
|
|
_meta['cover_url'] = ''
|
|
_meta['backdrop_path'] = ''
|
|
_meta['backdrop_url'] = ''
|
|
_meta['episode'] = 0
|
|
_meta['playcount'] = 0
|
|
|
|
if 'title' in meta and meta['title']:
|
|
_meta['title'] = meta['title']
|
|
elif 'name' in meta and meta['name']:
|
|
_meta['title'] = meta['name']
|
|
|
|
if 'id' in meta:
|
|
_meta['tmdb_id'] = meta['id']
|
|
if 'tmdb_id' in meta:
|
|
_meta['tmdb_id'] = meta['tmdb_id']
|
|
if 'imdb_id' in meta:
|
|
_meta['imdb_id'] = meta['imdb_id']
|
|
elif 'external_ids' in meta:
|
|
_meta['imdb_id'] = meta['external_ids']['imdb_id']
|
|
if 'mpaa' in meta:
|
|
_meta['mpaa'] = meta['mpaa']
|
|
if 'media_type' in meta:
|
|
_meta['media_type'] = meta['media_type']
|
|
|
|
if 'release_date' in meta:
|
|
_meta['premiered'] = meta['release_date']
|
|
elif 'first_air_date' in meta:
|
|
_meta['premiered'] = meta['first_air_date']
|
|
elif 'premiered' in meta and meta['premiered']:
|
|
_meta['premiered'] = meta['premiered']
|
|
elif 's_premiered' in meta and meta['s_premiered']:
|
|
_meta['premiered'] = meta['s_premiered']
|
|
elif 'air_date' in meta and meta['air_date']:
|
|
_meta['premiered'] = meta['air_date']
|
|
|
|
if 'year' in meta:
|
|
_meta['year'] = meta['year']
|
|
elif 's_year' in meta:
|
|
_meta['year'] = meta['s_year']
|
|
else:
|
|
try:
|
|
if 'premiered' in _meta and _meta['premiered']:
|
|
_meta['year'] = int(_meta['premiered'][:4])
|
|
except:
|
|
pass
|
|
|
|
if 'rating' in meta:
|
|
_meta['rating'] = meta['rating']
|
|
elif 'vote_average' in meta:
|
|
_meta['rating'] = meta['vote_average']
|
|
if 'votes' in meta:
|
|
_meta['votes'] = meta['votes']
|
|
elif 'vote_count' in meta:
|
|
_meta['votes'] = meta['vote_count']
|
|
|
|
try:
|
|
duration = 0
|
|
if 'runtime' in meta and meta['runtime']:
|
|
duration = int(meta['runtime'])
|
|
elif 'episode_run_time' in meta and meta['episode_run_time']:
|
|
duration = int(meta['episode_run_time'][0])
|
|
|
|
if duration < 300 : # en minutes
|
|
duration *= 60 # Convertir les minutes TMDB en secondes pour KODI
|
|
_meta['duration'] = duration
|
|
except:
|
|
_meta['duration'] = 0
|
|
|
|
if 'overview' in meta and meta['overview']:
|
|
_meta['plot'] = meta['overview']
|
|
elif 'parts' in meta: # Il s'agit d'une collection, on récupere le plot du premier film
|
|
_meta['plot'] = meta['parts'][0]['overview']
|
|
elif 'biography' in meta: # Il s'agit d'une personne, on récupere sa bio
|
|
_meta['plot'] = meta['biography']
|
|
|
|
if 'studio' in meta:
|
|
_meta['studio'] = meta['studio']
|
|
elif 'production_companies' in meta:
|
|
_meta['studio'] = ''
|
|
for studio in meta['production_companies']:
|
|
if _meta['studio'] == '':
|
|
_meta['studio'] += studio['name']
|
|
else:
|
|
_meta['studio'] += ' / ' + studio['name']
|
|
|
|
if 'genre' in meta:
|
|
listeGenre = meta['genre']
|
|
if '{' in listeGenre:
|
|
meta['genres'] = eval(listeGenre)
|
|
else:
|
|
_meta['genre'] = listeGenre
|
|
if 'genres' in meta:
|
|
# _meta['genre'] = ''
|
|
for genre in meta['genres']:
|
|
if _meta['genre'] == '':
|
|
_meta['genre'] += genre['name']
|
|
else:
|
|
_meta['genre'] += ' / ' + genre['name']
|
|
|
|
elif 'genre_ids' in meta:
|
|
genres = self.getGenresFromIDs(meta['genre_ids'])
|
|
_meta['genre'] = ''
|
|
for genre in genres:
|
|
if _meta['genre'] == '':
|
|
_meta['genre'] += genre
|
|
else:
|
|
_meta['genre'] += ' / ' + genre
|
|
|
|
if not isMatrix:
|
|
_meta['genre'] = unicode(_meta['genre'], 'utf-8')
|
|
|
|
elif 'parts' in meta: # Il s'agit d'une collection, on récupere le genre du premier film
|
|
genres = self.getGenresFromIDs(meta['parts'][0]['genre_ids'])
|
|
_meta['genre'] = ''
|
|
for genre in genres:
|
|
if _meta['genre'] == '':
|
|
_meta['genre'] += genre
|
|
else:
|
|
_meta['genre'] += ' / ' + genre
|
|
|
|
if not isMatrix:
|
|
_meta['genre'] = unicode(_meta['genre'], 'utf-8')
|
|
|
|
trailer_id = ''
|
|
if 'trailer' in meta and meta['trailer']: # Lecture du cache
|
|
_meta['trailer'] = meta['trailer']
|
|
elif 'trailers' in meta: # Trailer d'un film retourné par TMDB
|
|
try: # Recherche de la BA en français
|
|
trailers = meta['trailers']['youtube']
|
|
for trailer in trailers:
|
|
if trailer['type'] == 'Trailer':
|
|
if 'VF' in trailer['name']:
|
|
trailer_id = trailer['source']
|
|
break
|
|
# pas de trailer français, on prend le premier
|
|
if not trailer_id:
|
|
trailer_id = meta['trailers']['youtube'][0]['source']
|
|
_meta['trailer'] = self.URL_TRAILER % trailer_id
|
|
except:
|
|
pass
|
|
elif 'videos' in meta and meta['videos']: # Trailer d'une série retourné par TMDB
|
|
try: # Recherche de la BA en français
|
|
trailers = meta['videos']
|
|
if len(trailers['results']) >0:
|
|
for trailer in trailers['results']:
|
|
if trailer['type'] == 'Trailer' and trailer['site'] == 'YouTube':
|
|
trailer_id = trailer['key'] # Au moins c'est un trailer, pas forcement français
|
|
if 'fr' in trailer['iso_639_1']:
|
|
trailer_id = trailer['key']
|
|
break
|
|
# pas de trailer, on prend la premiere vidéo disponible
|
|
if not trailer_id:
|
|
trailer_id = meta['videos'][0]['key']
|
|
_meta['trailer'] = self.URL_TRAILER % trailer_id
|
|
except:
|
|
pass
|
|
|
|
if 'backdrop_path' in meta and meta['backdrop_path']:
|
|
_meta['backdrop_path'] = meta['backdrop_path']
|
|
_meta['backdrop_url'] = self.fanart + str(_meta['backdrop_path'])
|
|
elif 'parts' in meta: # Il s'agit d'une collection, on récupere le backdrop du dernier film
|
|
nbFilm = len(meta['parts'])
|
|
_meta['backdrop_path'] = meta['parts'][nbFilm-1]['backdrop_path']
|
|
_meta['backdrop_url'] = self.fanart + str(_meta['backdrop_path'])
|
|
|
|
if 'poster_path' in meta and meta['poster_path']:
|
|
_meta['poster_path'] = meta['poster_path']
|
|
_meta['cover_url'] = self.poster + str(_meta['poster_path'])
|
|
elif 'parts' in meta: # Il s'agit d'une collection, on récupere le poster du dernier film
|
|
nbFilm = len(meta['parts'])
|
|
_meta['poster_path'] = meta['parts'][nbFilm-1]['poster_path']
|
|
_meta['cover_url'] = self.fanart + str(_meta['poster_path'])
|
|
elif 'profile_path' in meta: # il s'agit d'une personne
|
|
_meta['poster_path'] = meta['profile_path']
|
|
_meta['cover_url'] = self.poster + str(_meta['poster_path'])
|
|
elif 'file_path' in meta: # il s'agit d'un network
|
|
_meta['poster_path'] = meta['file_path']
|
|
_meta['cover_url'] = self.poster + str(_meta['poster_path'])
|
|
_meta['backdrop_path'] = _meta['poster_path']
|
|
_meta['backdrop_url'] = self.fanart + str(_meta['backdrop_path'])
|
|
|
|
|
|
# special saisons
|
|
if 's_poster_path' in meta and meta['s_poster_path']:
|
|
_meta['poster_path'] = meta['s_poster_path']
|
|
_meta['cover_url'] = self.poster + str(meta['s_poster_path'])
|
|
|
|
if 'playcount' in meta:
|
|
_meta['playcount'] = meta['playcount']
|
|
if _meta['playcount'] == 6: # Anciennement 6 = unwatched
|
|
_meta['playcount'] = 0
|
|
else:
|
|
_meta['playcount'] = 0
|
|
|
|
if 'tagline' in meta and meta['tagline']:
|
|
_meta['tagline'] = meta['tagline']
|
|
|
|
if 'status' in meta:
|
|
_meta['status'] = meta['status']
|
|
|
|
if 'writer' in meta and meta['writer']:
|
|
_meta['writer'] = meta['writer']
|
|
|
|
if 'director' in meta and meta['director']:
|
|
_meta['director'] = meta['director']
|
|
|
|
if 'credits' in meta and meta['credits']:
|
|
|
|
# Transformation compatible pour lecture depuis le cache et retour de TMDB
|
|
strmeta = str(meta['credits'])
|
|
listCredits = eval(strmeta)
|
|
|
|
casts = listCredits['cast']
|
|
crews = []
|
|
|
|
if len(casts) > 0:
|
|
#licast = []
|
|
if 'crew' in listCredits:
|
|
crews = listCredits['crew']
|
|
if len(crews)>0:
|
|
_meta['credits'] = "{u'cast': " + str(casts) + ", u'crew': "+str(crews) + "}"
|
|
else:
|
|
_meta['credits'] = "{u'cast': " + str(casts) + '}'
|
|
# _meta['credits'] = "{u'cast': " + str(casts) + ", u'crew': "+str(crews) + "}"
|
|
# _meta['credits'] = 'u\'cast\': ' + str(casts) + ''
|
|
#for cast in casts:
|
|
# licast.append((cast['name'], cast['character'], self.poster + str(cast['profile_path']), str(cast['id'])))
|
|
#_meta['cast'] = licast
|
|
|
|
#if 'crew' in listCredits:
|
|
if len(crews) > 0:
|
|
for crew in crews:
|
|
if crew['job'] == 'Director':
|
|
_meta['director'] = crew['name']
|
|
elif crew['department'] == 'Writing':
|
|
if _meta['writer'] != '':
|
|
_meta['writer'] += ' / '
|
|
_meta['writer'] += '%s (%s)' % (crew['job'], crew['name'])
|
|
elif crew['department'] == 'Production' and 'Producer' in crew['job']:
|
|
if _meta['writer'] != '':
|
|
_meta['writer'] += ' / '
|
|
_meta['writer'] += '%s (%s)' % (crew['job'], crew['name'])
|
|
|
|
return _meta
|
|
|
|
def _clean_title(self, title):
|
|
title = re.sub('[^%s]' % (string.ascii_lowercase + string.digits), '', title.lower())
|
|
return title
|
|
|
|
def _cache_search(self, media_type, name, tmdb_id='', year='', season='', episode=''):
|
|
if media_type == 'movie':
|
|
sql_select = 'SELECT * FROM movie'
|
|
if tmdb_id:
|
|
sql_select = sql_select + ' WHERE tmdb_id = \'%s\'' % tmdb_id
|
|
else:
|
|
sql_select = sql_select + ' WHERE title = \'%s\'' % name
|
|
|
|
if year:
|
|
sql_select = sql_select + ' AND year = %s' % year
|
|
|
|
elif media_type == 'collection':
|
|
sql_select = 'SELECT * FROM movie'
|
|
if tmdb_id:
|
|
sql_select = sql_select + ' WHERE tmdb_id = \'%s\'' % tmdb_id
|
|
else:
|
|
if not name.endswith('saga'):
|
|
name += 'saga'
|
|
sql_select = sql_select + ' WHERE title = \'%s\'' % name
|
|
|
|
elif media_type == 'tvshow' or media_type == 'anime':
|
|
|
|
sql_select = 'SELECT * FROM tvshow'
|
|
if season:
|
|
sql_select = 'SELECT *, season.poster_path as s_poster_path, season.premiered as s_premiered, ' \
|
|
'season.year as s_year FROM tvshow LEFT JOIN season ON tvshow.imdb_id = season.imdb_id '
|
|
if tmdb_id:
|
|
sql_select = sql_select + ' WHERE tvshow.tmdb_id = \'%s\'' % tmdb_id
|
|
else:
|
|
sql_select = sql_select + ' WHERE tvshow.title = \'%s\'' % name
|
|
|
|
if year:
|
|
sql_select = sql_select + ' AND tvshow.year = %s' % year
|
|
|
|
if season:
|
|
sql_select = sql_select + ' AND season.season = \'%s\'' % season
|
|
else:
|
|
return None
|
|
|
|
|
|
try:
|
|
self.dbcur.execute(sql_select)
|
|
matchedrow = self.dbcur.fetchone()
|
|
except Exception as e:
|
|
VSlog('************* Error selecting from cache db: %s' % e, 4)
|
|
return None
|
|
|
|
if matchedrow:
|
|
# VSlog('Found meta information by name in cache table')
|
|
return dict(matchedrow)
|
|
else:
|
|
# VSlog('No match in local DB')
|
|
return None
|
|
|
|
def _cache_save(self, meta, name, media_type, season, year):
|
|
|
|
# Pas de cache pour les personnes ou les distributeurs
|
|
if media_type in ('person', 'network'):
|
|
return
|
|
|
|
# cache des séries et animes
|
|
if media_type == 'tvshow' or media_type == 'anime':
|
|
return self._cache_save_tvshow(meta, name, 'tvshow', season, year)
|
|
|
|
# cache des collections
|
|
if media_type == 'collection':
|
|
media_type = 'movie' # On utilise la même table que pour les films
|
|
if not name.endswith('saga'):
|
|
name += 'saga'
|
|
|
|
# sauvegarde de la durée en minutes, pour le retrouver en minutes comme le fait TMDB
|
|
runtime = 0
|
|
if 'duration' in meta and meta['duration']:
|
|
runtime = int(meta['duration'])/60
|
|
|
|
if not year and 'year' in meta:
|
|
year = meta['year']
|
|
|
|
# sauvegarde movie dans la BDD
|
|
# year n'est pas forcement l'année du film mais l'année utilisée pour la recherche
|
|
try:
|
|
sql = 'INSERT INTO %s (imdb_id, tmdb_id, title, year, credits, writer, director, tagline, vote_average, vote_count, runtime, ' \
|
|
'overview, mpaa, premiered, genre, studio, status, poster_path, trailer, backdrop_path, playcount) ' \
|
|
'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' % media_type
|
|
self.dbcur.execute(sql, (meta['imdb_id'], meta['tmdb_id'], name, year, meta['credits'], meta['writer'], meta['director'], meta['tagline'], meta['rating'], meta['votes'], str(runtime), meta['plot'], meta['mpaa'], meta['premiered'], meta['genre'], meta['studio'], meta['status'], meta['poster_path'], meta['trailer'], meta['backdrop_path'], 0))
|
|
self.db.commit()
|
|
# VSlog('SQL INSERT Successfully')
|
|
except Exception as e:
|
|
VSlog('SQL ERROR INSERT into table ' + media_type)
|
|
pass
|
|
|
|
# Cache pour les séries (et animes)
|
|
def _cache_save_tvshow(self, meta, name, media_type, season, year):
|
|
|
|
# ecrit les saisons dans la BDD
|
|
if 'seasons' in meta:
|
|
self._cache_save_season(meta, season)
|
|
del meta['seasons']
|
|
|
|
if not year and 'year' in meta:
|
|
year = meta['year']
|
|
|
|
# sauvegarde de la durée en minutes, pour le retrouver en minutes comme le fait TMDB
|
|
runtime = 0
|
|
if 'duration' in meta and meta['duration']:
|
|
runtime = int(meta['duration'])/60
|
|
|
|
# sauvegarde tvshow dans la BDD
|
|
try:
|
|
sql = 'INSERT INTO %s (imdb_id, tmdb_id, title, year, credits, writer, director, vote_average, vote_count, runtime, ' \
|
|
'overview, mpaa, premiered, genre, studio, status, poster_path, trailer, backdrop_path, playcount) ' \
|
|
'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' % media_type
|
|
self.dbcur.execute(sql, (meta['imdb_id'], meta['tmdb_id'], name, year, meta['credits'], meta['writer'], meta['director'], meta['rating'], meta['votes'], runtime, meta['plot'], meta['mpaa'], meta['premiered'], meta['genre'], meta['studio'], meta['status'], meta['poster_path'], meta['trailer'], meta['backdrop_path'], 0))
|
|
self.db.commit()
|
|
# VSlog('SQL INSERT Successfully')
|
|
except Exception as e:
|
|
VSlog('SQL ERROR INSERT into table ' + media_type)
|
|
pass
|
|
|
|
def _cache_save_season(self, meta, season):
|
|
|
|
for s in meta['seasons']:
|
|
if s['season_number'] != None and ('%02d' % int(s['season_number'])) == season:
|
|
meta['s_poster_path'] = s['poster_path']
|
|
meta['s_premiered'] = s['air_date']
|
|
meta['s_year'] = s['air_date']
|
|
|
|
try:
|
|
sql = 'INSERT INTO season (imdb_id, tmdb_id, season, year, premiered, poster_path, playcount) VALUES ' \
|
|
'(?, ?, ?, ?, ?, ?, ?) '
|
|
self.dbcur.execute(sql, (meta['imdb_id'], s['id'], s['season_number'], s['air_date'], s['air_date'], s['poster_path'], 6))
|
|
|
|
self.db.commit()
|
|
# VSlog('SQL INSERT Successfully')
|
|
except Exception:
|
|
VSlog('SQL ERROR INSERT into table season')
|
|
pass
|
|
|
|
def get_meta(self, media_type, name, imdb_id='', tmdb_id='', year='', season='', episode='', update=False):
|
|
"""
|
|
Main method to get meta data for movie or tvshow. Will lookup by name/year
|
|
if no IMDB ID supplied.
|
|
|
|
Args:
|
|
media_type (str): 'movie' or 'tvshow'
|
|
name (str): full name of movie/tvshow you are searching
|
|
Kwargs:
|
|
imdb_id (str): IMDB ID
|
|
tmdb_id (str): TMDB ID
|
|
year (str): 4 digit year of video, recommended to include the year whenever possible
|
|
to maximize correct search results.
|
|
season (int)
|
|
episode (int)
|
|
|
|
Returns:
|
|
DICT of meta data or None if cannot be found.
|
|
"""
|
|
|
|
name = re.sub(" +", " ", name) # nettoyage du titre
|
|
|
|
# VSlog('Attempting to retrieve meta data for %s: %s %s %s %s' % (media_type, name, year, imdb_id, tmdb_id))
|
|
|
|
# recherche dans la base de données
|
|
if not update:
|
|
meta = self._cache_search(media_type, self._clean_title(name), tmdb_id, year, season, episode)
|
|
if meta:
|
|
meta = self._format(meta, name)
|
|
return meta
|
|
|
|
# recherche online
|
|
meta = {}
|
|
if media_type == 'movie':
|
|
if tmdb_id:
|
|
meta = self.search_movie_id(tmdb_id)
|
|
elif name:
|
|
meta = self.search_movie_name(name, year)
|
|
elif media_type == 'tvshow':
|
|
if tmdb_id:
|
|
meta = self.search_tvshow_id(tmdb_id)
|
|
elif name:
|
|
meta = self.search_tvshow_name(name, year)
|
|
elif media_type == 'anime':
|
|
if tmdb_id:
|
|
meta = self.search_tvshow_id(tmdb_id)
|
|
elif name:
|
|
meta = self.search_tvshow_name(name, year, genre = 16)
|
|
elif media_type == 'collection':
|
|
if tmdb_id:
|
|
meta = self.search_collection_id(tmdb_id)
|
|
elif name:
|
|
meta = self.search_collection_name(name)
|
|
elif media_type == 'person':
|
|
if tmdb_id:
|
|
meta = self.search_person_id(tmdb_id)
|
|
elif name:
|
|
meta = self.search_person_name(name)
|
|
elif media_type == 'network':
|
|
if tmdb_id:
|
|
meta = self.search_network_id(tmdb_id)
|
|
|
|
# Mise en forme des metas si trouvé
|
|
if meta and 'tmdb_id' in meta:
|
|
meta = self._format(meta, name)
|
|
# sauvegarde dans un cache
|
|
self._cache_save(meta, self._clean_title(name), media_type, season, year)
|
|
else: # initialise un meta vide
|
|
meta = self._format(meta, name)
|
|
|
|
return meta
|
|
|
|
def getUrl(self, url, page=1, term=''):
|
|
# return url api exemple 'movie/popular' page en cours
|
|
try:
|
|
if term:
|
|
term = term + '&page=' + str(page)
|
|
else:
|
|
term = 'page=' + str(page)
|
|
result = self._call(url, term)
|
|
except:
|
|
return False
|
|
return result
|
|
|
|
def _call(self, action, append_to_response=''):
|
|
url = '%s%s?language=%s&api_key=%s' % (self.URL, action, self.lang, self.api_key)
|
|
if append_to_response:
|
|
url += '&%s' % append_to_response
|
|
|
|
#On utilise requests car urllib n'arrive pas a certain moment a ouvrir le json.
|
|
import requests
|
|
data = requests.get(url).json()
|
|
|
|
return data
|
|
|
|
def getPostUrl(self, action, post):
|
|
|
|
tmdb_session = self.ADDON.getSetting('tmdb_session')
|
|
if not tmdb_session:
|
|
return
|
|
|
|
sUrl = '%s%s?api_key=%s&session_id=%s' % (self.URL, action, self.api_key, tmdb_session)
|
|
try:
|
|
sPost = json.dumps(post).encode('utf-8')
|
|
except:
|
|
sPost = json.dumps(post)
|
|
|
|
headers = {'Content-Type': 'application/json'}
|
|
req = urllib2.Request(sUrl, sPost, headers)
|
|
response = urllib2.urlopen(req)
|
|
data = json.loads(response.read())
|
|
return data
|
|
|
|
# retourne la liste des genres en Texte, à partir des IDs
|
|
def getGenresFromIDs(self, genresID):
|
|
sGenres = []
|
|
for gid in genresID:
|
|
genre = self.TMDB_GENRES.get(gid)
|
|
if genre:
|
|
sGenres.append(genre)
|
|
return sGenres
|
|
|
|
# Retourne le genre en Texte, à partir d'un ID
|
|
def getGenreFromID(self, genreID):
|
|
if not str(genreID).isdigit():
|
|
return genreID
|
|
|
|
genre = self.TMDB_GENRES.get(genreID)
|
|
if genre:
|
|
return genre
|
|
return genreID
|
|
|