Browse Source
Changelog --------- Change improve loading speed of shows at startup. Change improve main execution loop speed. Add force cast update to view show page. Add person view. Add character view. Add characters, person to clean-up cache (30 days). Add reload person, character images every 7 days. Add suppress UI notification for scheduled people updates during show updates and during switching ids. Add resume support of switched shows after restart that switched id but not finished updating. Add failed TV info switches to show tasks page. Add remove item from queue and clear queue test buttons to mange/show-tasks and manage/search-tasks. Change improve show update logic. Add to view-show page a notification message if a show fails to switch. Add check for existing show with new id pair before switching. Change prioritize first episode start year over the start year set at the tv info source. Change delete non existing episodes when switching indexer. Change "exists in db" link on search results page to support any info source. Add TMDB person pics as fallback. Add use person fallback for character images. Add logic to add start, end year in case of multiple characters per person. Change improve speed getting list in the import view. Add abort people cast update when show is deleted, also remove show from any queued show item or search item Support list of names to search for in show search. Change assist user search terms when the actual title of a show is unknown. Add support URLs in search. Change remove year from search term ... year is still used for relevancy order. Add updating show.nfo when a cast changes. Change use longest biography available for output. Add UI requests of details for feb 28 will also return feb 29 in years without feb 29. Add fetch extra data fallback from TMDB for persons. technical commit messages (combined commits) -------------------------------------------- Add tmdb get_trending, get_popular, get_top_rated, discover to tvinfoapi. Add home/get_persons ajax endpoint, currently supports: birthday, deathday, names. Change daily-schedule to new get_episode_time - More TODO Change view-show to use get_episode_time. Add get show updates list in show updater. Add get_episode_time to network_timezones. Add airtime for episode and show to get_episode_time. Small ep obj load performance improvement. Add handle special episodes and assign numbers based on airdate. Add handle tvmaze specials without airdate. Change during switch tv info source, specials are removed from db because of non-existing generic numbering. Change add first/latest regular episode, use first_aired_regular_episode in all places that have airdate of first episode. Add IMDb to person interface. Add akas to person. Add IMDb bio parser. Add TMDB api for people. Add character role start/end year to IMDb api and tv character. Fix updating characters with multiple persons, by limiting to one. Add cache to imdb_api.py. Add cache to tmdb_api: get_person, _search_person. Add cache to trakt get_person, _search_person cache. Improve main execution loop speed https://stackify.com/20-simple-python-performance-tuning-tips/ point: 12 Add network to episode for tvdb_api (from show data). Add fallback for network/timezone for tv episode to tv show. Add skip retrieve_exceptions for tv info sources that don't have 'scene_url'. Change move network load before show load to make sure the tvshow obj timezone can be set (startup). Add datetime.time to/from integer to sbdatetime (hour, minute only). Add load all indexer mapping at once from db during startup. Add load the failed count during startup. Change move sanitize_filename to sg_helpers. Change move download_file to sg_helpers. Add list_tables, list_indexes to db.py. Add multi db column add function. Restore backup tables during upgrade. Use lib.tvinfo_base import everywhere. Add new properties to tvepisode. Add show_updates to indexer endpoint. Add new db schema. Add Character and Persons tables. Add tvmaze_api lib. Add pytvmaze lib. Add debug __repr__ to people/show queue. Add crew to show tvinfo_base. Drop backup tables for now. Don't save switch refresh, update show queue items (since they are sub queues of switch). Remove show from switched_shows in case it is in it when deleting the show. Use switch queue for manual switch Load/save updated time for person/Character. Add show_queue table. Add _get_item_sql, _delete_item_from_db_sql to search search_queue. Add people_queue type. Add people scheduler. Add people queue. Add tvinfo source switch queue item. Alternate naming for person/character images. Add load and save image/thumb urls for persons. Add load character pics. Add save images for characters. Add save image urls for characters to db. Change limit for person searches to 100 instead of 10 default. Add person verification. Add people_url and character_url to sources that support them. Add save castlist changes. Add remove old characters from castlist. Change optimize find_show_by_id. Change improve debug info for person, character obj. Add db_support_upsert support flag in db.py. Add cast list objs and load from db. Add parse and add additional images to TVInfo. Add optional loading of images and actors/crew. Fix _make_timestamp in py2. Save and load _src_update_time. Change use update time for show updates. Add _set_network optimization. Add src_update_timestamp to tvshow tbl. Add updated_timestamp to TVInfoShow. Add _indexer_update_time TVShow obj. Add support to search for external ids on TV info sources: [TVINFO_TVDB, TVINFO_IMDb, TVINFO_TMDB, TVINFO_TRAKT]. Change show tasks page, keep remove button for failed switches always visible. Add use get_url for tvmaze_api interface (to support failure handling), without changing original lib. Add missing settings in switch show. Add new switch error: TVSWITCH_ID_CONFLICT: 'new id conflicts with existing show'. Add messages for manual switch id. Add connection skip handling. Add get_url failure handling to Trakt lib. Add real search_person to tvinfo interface. Change move TMDB api key to tmdb_api. Add get_url usage via tmdb_api. Change join/split of akas/join and sql group_concat to `;;;` to prevent issues with names that may contain comma (`,`). Add Trakt api specific failure times. Add warning about reassigning MEMCACHE Add calc_age to sg_helpers for person page to remove dupe code. Add return age in ajax for persons. Change format dates on person page to honour config/General. Add convert_to_inch_fraction_html to sg_helpers. Change direct assign method instead of wrapper (faster). Change force disk_pickle_protocol=2 for py2 compatibility in tvinfo cache. Add episode rename info during switch. Add save deleted episodes when switching tvinfo source. Add get_switch_changed page. Add force=True to QueueItemRefresh during switch of tvinfo to force rewriting of metadata. Add filter character dupes in tvdb_api only take images for unique character, role combos on tvdb. Fix humanize lib in py27. Add diskcache to tvinfo_base. Add doc files for diskcache lib. Add clean tvinfo to show_updater. Add switch_ep_errors table to sickbeard.db Add debug log message when extra data is fetched for a person. Change make character loading more efficient. Add placeholder image for characters and persons. Change start, end year moved to new extra table to support multiple person per character. Change replace start_year, end_year with persons_years in Character class. Add new table character_person_years, change table structure characters. Add check if show is found for switch pages. Add missing scheduled to cache.db people queue table. Change trakt show search now will ignore failure handling, to make sure it's always tried when searching for shows. Change add/improve tvmaze id cross search. Change improve search, if only ids are given, resulting seriesnames on source will be used as text search on following tvinfo sources. Add new parameter: prefer_person to imagecache/character endpoint. Add if prefer_person is set and person_id is set to valid person and the character has more then 1 person assigned the character image will not be returned, instead the actors image or the placeholder. Add only take external ids if character is confirmed in logic. Add match name instead of person id for checking same person in show when adding cast for sources without person id (tvdb) Add cache tmdb genres directly in dict for performance.tags/release_0.25.1
committed by
JackDandy
79 changed files with 9568 additions and 1170 deletions
After Width: | Height: | Size: 265 B |
After Width: | Height: | Size: 753 B |
After Width: | Height: | Size: 374 B |
After Width: | Height: | Size: 142 B |
@ -0,0 +1,65 @@ |
|||
#import sickbeard |
|||
#from sickbeard import TVInfoAPI |
|||
#from sickbeard.helpers import anon_url |
|||
#from six import iteritems |
|||
<% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp# |
|||
<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp# |
|||
## |
|||
#set global $title = 'Character' |
|||
#set global $header = 'Character' |
|||
#set global $sbPath = '../..' |
|||
#set global $topmenu = 'Character' |
|||
## |
|||
#import os.path |
|||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') |
|||
|
|||
#if $varExists('header') |
|||
<h1 class="header">$header</h1> |
|||
#else |
|||
<h1 class="title">$title</h1> |
|||
#end if |
|||
|
|||
## |
|||
#set $html_selected = ' selected="selected"' |
|||
#set $html_checked = ' checked="checked"' |
|||
<div id="character"> |
|||
<div id="character-content" class="linefix"> |
|||
<a href="$sbRoot/imagecache/character?character_id=$character.id&show=$show_obj.tvid_prodid&thumb=0" rel="dialog"><img src="$sbRoot/imagecache/character?character_id=$character.id&show=$show_obj.tvid_prodid&thumb=1" style="height: 300px;"></a> |
|||
<div style="font-weight: bolder;font-size: x-large;">$character.name</div> |
|||
#if $character.person |
|||
<div style="font-weight:lighter;font-size: large;">( |
|||
#set $p_count = len($character.person) |
|||
#for $p_nb, $person in enumerate($character.person, 1) |
|||
<a href="$sbRoot/home/person?person_id=$person.id" >$person.name</a> |
|||
#if $p_nb < $p_count |
|||
<span>, </span> |
|||
#end if |
|||
#end for |
|||
)</div> |
|||
#end if |
|||
<br /> |
|||
<div><span style="font-weight: bolder; font-size: larger;">Bio:</span><br />$character.biography</div> |
|||
<br /> |
|||
<div><span style="font-weight: bolder; font-size: larger;">Links:</span><br /> |
|||
#for $src, $sid in sorted(iteritems($character.ids)) |
|||
#if $TVInfoAPI($src).config.get('character_url') |
|||
<a href="$anon_url($TVInfoAPI($src).config['character_url'] % $sid)" target="_blank"> |
|||
#if $TVInfoAPI($src).config.get('icon') |
|||
<img alt="$TVInfoAPI($src).name" height="16" width="16" src="$sbRoot/images/$TVInfoAPI($src).config['icon']"> |
|||
#end if |
|||
$TVInfoAPI($src).name</a><br /> |
|||
#end if |
|||
#end for |
|||
</div> |
|||
<br /> |
|||
<div><span style="font-weight: bolder; font-size: x-large;">Shows featuring this Character in db:</span><br /> |
|||
#for $character in $characters |
|||
<div style="margin: 10px;font-size: x-large;"><span style="width: 210px;display: inline-block;"><a href="$sbRoot/imagecache/character?character_id=$character['character_id']&show=$character['show_obj'].tvid_prodid&thumb=0" rel="dialog"><img src="$sbRoot/imagecache/character?character_id=$character['character_id']&show=$character['show_obj'].tvid_prodid&thumb=1" style="height: 200px;"></a></span>$character['character_name'] <span style="font-weight: lighter;">(<a href="$sbRoot/home/view-show?tvid_prodid=$character['show_obj'].tvid_prodid">$character['show_obj'].name</a>)</span></div> |
|||
#end for |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div></div> |
|||
|
|||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl') |
@ -0,0 +1,81 @@ |
|||
#import sickbeard |
|||
#from sickbeard import TVInfoAPI |
|||
#from sickbeard.helpers import anon_url |
|||
#from sickbeard.tv import PersonGenders |
|||
#from six import iteritems |
|||
<% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp# |
|||
<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp# |
|||
## |
|||
#set global $title = 'Person' |
|||
#set global $header = 'Person' |
|||
#set global $sbPath = '../..' |
|||
#set global $topmenu = 'person' |
|||
## |
|||
#import os.path |
|||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') |
|||
|
|||
#if $varExists('header') |
|||
<h1 class="header">$header</h1> |
|||
#else |
|||
<h1 class="title">$title</h1> |
|||
#end if |
|||
|
|||
## |
|||
#set $html_selected = ' selected="selected"' |
|||
#set $html_checked = ' checked="checked"' |
|||
#set $age = $person.age |
|||
<div id="person"> |
|||
<div id="person-content" class="linefix"> |
|||
<a href="$sbRoot/imagecache/person?person_id=$person.id&thumb=0" rel="dialog"><img src="$sbRoot/imagecache/person?person_id=$person.id&thumb=1" style="height: 300px;"></a> |
|||
<div style="font-weight: bolder;font-size: x-large;">$person.name#if $age #<span> ($age)</span>#end if#</div> |
|||
#if $PersonGenders.UNKNOWN != $person.gender |
|||
<div><span style="font-weight: bolder;">Gender:</span> $PersonGenders.names.get($person.gender, 'unknown')</div> |
|||
#end if |
|||
#if $person.birthday |
|||
<div><span style="font-weight: bolder;">Birthdate:</span> $person.birthday</div> |
|||
#end if |
|||
#if $person.birthplace |
|||
<div><span style="font-weight: bolder;">Birthplace:</span> $person.birthplace</div> |
|||
#end if |
|||
#if $person.deathday |
|||
<div><span style="font-weight: bolder;">Deathdate:</span> $person.deathday</div> |
|||
#end if |
|||
<br /> |
|||
<div><span style="font-weight: bolder; font-size: larger;">Bio:</span><br />$person.biography</div> |
|||
<br /> |
|||
<div><span style="font-weight: bolder; font-size: larger;">Links:</span><br /> |
|||
#for $src, $sid in sorted(iteritems($person.ids)) |
|||
#if $TVInfoAPI($src).config.get('people_url') |
|||
<a href="$anon_url($TVInfoAPI($src).config['people_url'] % $sid)" target="_blank"> |
|||
#if $TVInfoAPI($src).config.get('icon') |
|||
<img alt="$TVInfoAPI($src).name" height="16" width="16" src="$sbRoot/images/$TVInfoAPI($src).config['icon']"> |
|||
#end if |
|||
$TVInfoAPI($src).name</a><br /> |
|||
#end if |
|||
#end for |
|||
</div> |
|||
<br /> |
|||
<div><span style="font-weight: bolder; font-size: x-large;">Characters in db:</span><br /> |
|||
#for $character in $characters |
|||
#if $character.get('show_obj') |
|||
<div style="margin: 10px;font-size: x-large;"><span style="width: 210px;display: inline-block;"><a href="$sbRoot/imagecache/character?character_id=$character['character_id']&show=$character['show_obj'].tvid_prodid&thumb=0" rel="dialog"><img src="$sbRoot/imagecache/character?character_id=$character['character_id']&show=$character['show_obj'].tvid_prodid&thumb=1" style="height: 200px;"></a></span><a href="$sbRoot/home/character?character_id=$character['character_id']&show=$character['show_obj'].tvid_prodid&thumb=0" style="font-weight: bolder;">$character['character_name']</a> <span style="font-weight: lighter;">(<a href="$sbRoot/home/view-show?tvid_prodid=$character['show_obj'].tvid_prodid">$character['show_obj'].name</a>)</span> |
|||
#if $character.get('show_obj') |
|||
#set $first_aired = $character['show_obj'].first_aired_episode |
|||
#set $latest_aired = $character['show_obj'].latest_aired_episode |
|||
#if $first_aired |
|||
#set $from_age = $person.calc_age($first_aired.airdate) |
|||
#if $from_age |
|||
($from_age#if $latest_aired# - $person.calc_age($latest_aired.airdate)#end if# years) |
|||
#end if |
|||
#end if |
|||
#end if |
|||
</div> |
|||
#end if |
|||
#end for |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div></div> |
|||
|
|||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl') |
@ -0,0 +1,23 @@ |
|||
#import datetime |
|||
#import sickbeard |
|||
#from sickbeard import TVInfoAPI |
|||
#from sickbeard.helpers import anon_url |
|||
#from sickbeard.tv import PersonGenders |
|||
#from six import iteritems |
|||
<% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp# |
|||
<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp# |
|||
## |
|||
#set global $title = 'Show Switch Errors' |
|||
#set global $header = 'Show Switch Errors' |
|||
#set global $sbPath = '../..' |
|||
#set global $topmenu = 'show' |
|||
## |
|||
#import os.path |
|||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') |
|||
|
|||
<h4>Shows with changed episodes</h4> |
|||
#for $show_obj, $show_data in $iteritems($show_list) |
|||
<div><a href="$sbRoot/home/view-show?tvid_prodid=$show_obj.tvid_prodid">$show_obj.name</a> - <a href="$sbRoot/home/get-switch-changed-episodes?tvid_prodid=$show_obj.tvid_prodid">($show_data.get('changed', 0) changed, $show_data.get('deleted', 0) deleted episodes)</a></div> |
|||
#end for |
|||
|
|||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl') |
@ -0,0 +1,25 @@ |
|||
#import datetime |
|||
#import sickbeard |
|||
#from sickbeard import TVInfoAPI |
|||
#from sickbeard.helpers import anon_url |
|||
#from sickbeard.tv import PersonGenders |
|||
#from six import iteritems |
|||
<% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp# |
|||
<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp# |
|||
## |
|||
#set global $title = 'Show Switch Changes' |
|||
#set global $header = 'Show Switch Changes' |
|||
#set global $sbPath = '../..' |
|||
#set global $topmenu = 'show' |
|||
## |
|||
#import os.path |
|||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') |
|||
|
|||
<h4>$show_obj.name - Changes</h4> |
|||
#for $episode in $ep_list |
|||
#set $ep_obj = $episode['ep_obj'] |
|||
#set $ep_name = ($ep_obj and $ep_obj.name) or '' |
|||
<div>$episode['reason'] <a href="$sbRoot/home/view-show?tvid_prodid=$show_obj.tvid_prodid#season-$episode['season']">$ep_name #echo '%sx%s' % ($episode['season'], $episode['episode'])#</a></div> |
|||
#end for |
|||
|
|||
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl') |
@ -0,0 +1,5 @@ |
|||
from .core import Cache as Cache, DEFAULT_SETTINGS as DEFAULT_SETTINGS, Disk as Disk, ENOVAL as ENOVAL, EVICTION_POLICY as EVICTION_POLICY, EmptyDirWarning as EmptyDirWarning, JSONDisk as JSONDisk, Timeout as Timeout, UNKNOWN as UNKNOWN, UnknownFileWarning as UnknownFileWarning |
|||
from .djangocache import DjangoCache as DjangoCache |
|||
from .fanout import FanoutCache as FanoutCache |
|||
from .persistent import Deque as Deque, Index as Index |
|||
from .recipes import Averager as Averager, BoundedSemaphore as BoundedSemaphore, Lock as Lock, RLock as RLock, barrier as barrier, memoize_stampede as memoize_stampede, throttle as throttle |
@ -0,0 +1,112 @@ |
|||
from typing import Any, Optional |
|||
|
|||
def full_name(func: Any): ... |
|||
|
|||
class WindowsError(Exception): ... |
|||
|
|||
class Constant(tuple): |
|||
def __new__(cls, name: Any): ... |
|||
def __repr__(self): ... |
|||
|
|||
DBNAME: str |
|||
ENOVAL: Any |
|||
UNKNOWN: Any |
|||
MODE_NONE: int |
|||
MODE_RAW: int |
|||
MODE_BINARY: int |
|||
MODE_TEXT: int |
|||
MODE_PICKLE: int |
|||
DEFAULT_SETTINGS: Any |
|||
METADATA: Any |
|||
EVICTION_POLICY: Any |
|||
|
|||
class Disk: |
|||
_directory: Any = ... |
|||
min_file_size: Any = ... |
|||
pickle_protocol: Any = ... |
|||
def __init__(self, directory: Any, min_file_size: int = ..., pickle_protocol: int = ...) -> None: ... |
|||
def hash(self, key: Any): ... |
|||
def put(self, key: Any): ... |
|||
def get(self, key: Any, raw: Any): ... |
|||
def store(self, value: Any, read: Any, key: Any = ...): ... |
|||
def fetch(self, mode: Any, filename: Any, value: Any, read: Any): ... |
|||
def filename(self, key: Any = ..., value: Any = ...): ... |
|||
def remove(self, filename: Any) -> None: ... |
|||
|
|||
class JSONDisk(Disk): |
|||
compress_level: Any = ... |
|||
def __init__(self, directory: Any, compress_level: int = ..., **kwargs: Any) -> None: ... |
|||
def put(self, key: Any): ... |
|||
def get(self, key: Any, raw: Any): ... |
|||
def store(self, value: Any, read: Any, key: Any = ...): ... |
|||
def fetch(self, mode: Any, filename: Any, value: Any, read: Any): ... |
|||
|
|||
class Timeout(Exception): ... |
|||
class UnknownFileWarning(UserWarning): ... |
|||
class EmptyDirWarning(UserWarning): ... |
|||
|
|||
def args_to_key(base: Any, args: Any, kwargs: Any, typed: Any): ... |
|||
|
|||
class Cache: |
|||
_directory: Any = ... |
|||
_timeout: int = ... |
|||
_local: Any = ... |
|||
_txn_id: Any = ... |
|||
_disk: Any = ... |
|||
def __init__(self, directory: Optional[Any] = ..., timeout: int = ..., disk: Any = ..., **settings: Any) -> None: ... |
|||
@property |
|||
def directory(self): ... |
|||
@property |
|||
def timeout(self): ... |
|||
@property |
|||
def disk(self): ... |
|||
@property |
|||
def _con(self): ... |
|||
@property |
|||
def _sql(self): ... |
|||
@property |
|||
def _sql_retry(self): ... |
|||
def transact(self, retry: bool = ...) -> None: ... |
|||
def _transact(self, retry: bool = ..., filename: Optional[Any] = ...) -> None: ... |
|||
def set(self, key: Any, value: Any, expire: Optional[Any] = ..., read: bool = ..., tag: Optional[Any] = ..., retry: bool = ...): ... |
|||
def __setitem__(self, key: Any, value: Any) -> None: ... |
|||
def _row_update(self, rowid: Any, now: Any, columns: Any) -> None: ... |
|||
def _row_insert(self, key: Any, raw: Any, now: Any, columns: Any) -> None: ... |
|||
def _cull(self, now: Any, sql: Any, cleanup: Any, limit: Optional[Any] = ...) -> None: ... |
|||
def touch(self, key: Any, expire: Optional[Any] = ..., retry: bool = ...): ... |
|||
def add(self, key: Any, value: Any, expire: Optional[Any] = ..., read: bool = ..., tag: Optional[Any] = ..., retry: bool = ...): ... |
|||
def incr(self, key: Any, delta: int = ..., default: int = ..., retry: bool = ...): ... |
|||
def decr(self, key: Any, delta: int = ..., default: int = ..., retry: bool = ...): ... |
|||
def get(self, key: Any, default: Optional[Any] = ..., read: bool = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def __getitem__(self, key: Any): ... |
|||
def read(self, key: Any, retry: bool = ...): ... |
|||
def __contains__(self, key: Any): ... |
|||
def pop(self, key: Any, default: Optional[Any] = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def __delitem__(self, key: Any, retry: bool = ...): ... |
|||
def delete(self, key: Any, retry: bool = ...): ... |
|||
def push(self, value: Any, prefix: Optional[Any] = ..., side: str = ..., expire: Optional[Any] = ..., read: bool = ..., tag: Optional[Any] = ..., retry: bool = ...): ... |
|||
def pull(self, prefix: Optional[Any] = ..., default: Any = ..., side: str = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def peek(self, prefix: Optional[Any] = ..., default: Any = ..., side: str = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def peekitem(self, last: bool = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def memoize(self, name: Optional[Any] = ..., typed: bool = ..., expire: Optional[Any] = ..., tag: Optional[Any] = ...): ... |
|||
def check(self, fix: bool = ..., retry: bool = ...): ... |
|||
def create_tag_index(self) -> None: ... |
|||
def drop_tag_index(self) -> None: ... |
|||
def evict(self, tag: Any, retry: bool = ...): ... |
|||
def expire(self, now: Optional[Any] = ..., retry: bool = ...): ... |
|||
def cull(self, retry: bool = ...): ... |
|||
def clear(self, retry: bool = ...): ... |
|||
def _select_delete(self, select: Any, args: Any, row_index: int = ..., arg_index: int = ..., retry: bool = ...): ... |
|||
def iterkeys(self, reverse: bool = ...) -> None: ... |
|||
def _iter(self, ascending: bool = ...) -> None: ... |
|||
def __iter__(self) -> Any: ... |
|||
def __reversed__(self): ... |
|||
def stats(self, enable: bool = ..., reset: bool = ...): ... |
|||
def volume(self): ... |
|||
def close(self) -> None: ... |
|||
def __enter__(self): ... |
|||
def __exit__(self, *exception: Any) -> None: ... |
|||
def __len__(self): ... |
|||
def __getstate__(self): ... |
|||
def __setstate__(self, state: Any) -> None: ... |
|||
def reset(self, key: Any, value: Any = ..., update: bool = ...): ... |
@ -0,0 +1,33 @@ |
|||
from .core import ENOVAL as ENOVAL, args_to_key as args_to_key, full_name as full_name |
|||
from .fanout import FanoutCache as FanoutCache |
|||
from django.core.cache.backends.base import BaseCache |
|||
from typing import Any, Optional |
|||
|
|||
class DjangoCache(BaseCache): |
|||
_cache: Any = ... |
|||
def __init__(self, directory: Any, params: Any) -> None: ... |
|||
@property |
|||
def directory(self): ... |
|||
def cache(self, name: Any): ... |
|||
def deque(self, name: Any): ... |
|||
def index(self, name: Any): ... |
|||
def add(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ..., read: bool = ..., tag: Optional[Any] = ..., retry: bool = ...): ... |
|||
def get(self, key: Any, default: Optional[Any] = ..., version: Optional[Any] = ..., read: bool = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def read(self, key: Any, version: Optional[Any] = ...): ... |
|||
def set(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ..., read: bool = ..., tag: Optional[Any] = ..., retry: bool = ...): ... |
|||
def touch(self, key: Any, timeout: Any = ..., version: Optional[Any] = ..., retry: bool = ...): ... |
|||
def pop(self, key: Any, default: Optional[Any] = ..., version: Optional[Any] = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def delete(self, key: Any, version: Optional[Any] = ..., retry: bool = ...) -> None: ... |
|||
def incr(self, key: Any, delta: int = ..., version: Optional[Any] = ..., default: Optional[Any] = ..., retry: bool = ...): ... |
|||
def decr(self, key: Any, delta: int = ..., version: Optional[Any] = ..., default: Optional[Any] = ..., retry: bool = ...): ... |
|||
def has_key(self, key: Any, version: Optional[Any] = ...): ... |
|||
def expire(self): ... |
|||
def stats(self, enable: bool = ..., reset: bool = ...): ... |
|||
def create_tag_index(self) -> None: ... |
|||
def drop_tag_index(self) -> None: ... |
|||
def evict(self, tag: Any): ... |
|||
def cull(self): ... |
|||
def clear(self): ... |
|||
def close(self, **kwargs: Any) -> None: ... |
|||
def get_backend_timeout(self, timeout: Any = ...): ... |
|||
def memoize(self, name: Optional[Any] = ..., timeout: Any = ..., version: Optional[Any] = ..., typed: bool = ..., tag: Optional[Any] = ...): ... |
@ -0,0 +1,52 @@ |
|||
from .core import Cache as Cache, DEFAULT_SETTINGS as DEFAULT_SETTINGS, Disk as Disk, ENOVAL as ENOVAL, Timeout as Timeout |
|||
from .persistent import Deque as Deque, Index as Index |
|||
from typing import Any, Optional |
|||
|
|||
class FanoutCache: |
|||
_count: Any = ... |
|||
_directory: Any = ... |
|||
_shards: Any = ... |
|||
_hash: Any = ... |
|||
_caches: Any = ... |
|||
_deques: Any = ... |
|||
_indexes: Any = ... |
|||
def __init__(self, directory: Optional[Any] = ..., shards: int = ..., timeout: float = ..., disk: Any = ..., **settings: Any) -> None: ... |
|||
@property |
|||
def directory(self): ... |
|||
def __getattr__(self, name: Any): ... |
|||
def transact(self, retry: bool = ...) -> None: ... |
|||
def set(self, key: Any, value: Any, expire: Optional[Any] = ..., read: bool = ..., tag: Optional[Any] = ..., retry: bool = ...): ... |
|||
def __setitem__(self, key: Any, value: Any) -> None: ... |
|||
def touch(self, key: Any, expire: Optional[Any] = ..., retry: bool = ...): ... |
|||
def add(self, key: Any, value: Any, expire: Optional[Any] = ..., read: bool = ..., tag: Optional[Any] = ..., retry: bool = ...): ... |
|||
def incr(self, key: Any, delta: int = ..., default: int = ..., retry: bool = ...): ... |
|||
def decr(self, key: Any, delta: int = ..., default: int = ..., retry: bool = ...): ... |
|||
def get(self, key: Any, default: Optional[Any] = ..., read: bool = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def __getitem__(self, key: Any): ... |
|||
def read(self, key: Any): ... |
|||
def __contains__(self, key: Any): ... |
|||
def pop(self, key: Any, default: Optional[Any] = ..., expire_time: bool = ..., tag: bool = ..., retry: bool = ...): ... |
|||
def delete(self, key: Any, retry: bool = ...): ... |
|||
def __delitem__(self, key: Any) -> None: ... |
|||
def check(self, fix: bool = ..., retry: bool = ...): ... |
|||
def expire(self, retry: bool = ...): ... |
|||
def create_tag_index(self) -> None: ... |
|||
def drop_tag_index(self) -> None: ... |
|||
def evict(self, tag: Any, retry: bool = ...): ... |
|||
def cull(self, retry: bool = ...): ... |
|||
def clear(self, retry: bool = ...): ... |
|||
def _remove(self, name: Any, args: Any = ..., retry: bool = ...): ... |
|||
def stats(self, enable: bool = ..., reset: bool = ...): ... |
|||
def volume(self): ... |
|||
def close(self) -> None: ... |
|||
def __enter__(self): ... |
|||
def __exit__(self, *exception: Any) -> None: ... |
|||
def __getstate__(self): ... |
|||
def __setstate__(self, state: Any) -> None: ... |
|||
def __iter__(self) -> Any: ... |
|||
def __reversed__(self): ... |
|||
def __len__(self): ... |
|||
def reset(self, key: Any, value: Any = ...): ... |
|||
def cache(self, name: Any): ... |
|||
def deque(self, name: Any): ... |
|||
def index(self, name: Any): ... |
@ -0,0 +1,81 @@ |
|||
from .core import Cache as Cache, ENOVAL as ENOVAL |
|||
from collections.abc import MutableMapping, Sequence |
|||
from typing import Any, Optional |
|||
|
|||
def _make_compare(seq_op: Any, doc: Any): ... |
|||
|
|||
class Deque(Sequence): |
|||
_cache: Any = ... |
|||
def __init__(self, iterable: Any = ..., directory: Optional[Any] = ...) -> None: ... |
|||
@classmethod |
|||
def fromcache(cls, cache: Any, iterable: Any = ...): ... |
|||
@property |
|||
def cache(self): ... |
|||
@property |
|||
def directory(self): ... |
|||
def _index(self, index: Any, func: Any): ... |
|||
def __getitem__(self, index: Any): ... |
|||
def __setitem__(self, index: Any, value: Any): ... |
|||
def __delitem__(self, index: Any) -> None: ... |
|||
def __repr__(self): ... |
|||
__eq__: Any = ... |
|||
__ne__: Any = ... |
|||
__lt__: Any = ... |
|||
__gt__: Any = ... |
|||
__le__: Any = ... |
|||
__ge__: Any = ... |
|||
def __iadd__(self, iterable: Any): ... |
|||
def __iter__(self) -> Any: ... |
|||
def __len__(self): ... |
|||
def __reversed__(self) -> None: ... |
|||
def __getstate__(self): ... |
|||
def __setstate__(self, state: Any) -> None: ... |
|||
def append(self, value: Any) -> None: ... |
|||
def appendleft(self, value: Any) -> None: ... |
|||
def clear(self) -> None: ... |
|||
def count(self, value: Any): ... |
|||
def extend(self, iterable: Any) -> None: ... |
|||
def extendleft(self, iterable: Any) -> None: ... |
|||
def peek(self): ... |
|||
def peekleft(self): ... |
|||
def pop(self): ... |
|||
def popleft(self): ... |
|||
def remove(self, value: Any) -> None: ... |
|||
def reverse(self) -> None: ... |
|||
def rotate(self, steps: int = ...) -> None: ... |
|||
__hash__: Any = ... |
|||
def transact(self) -> None: ... |
|||
|
|||
class Index(MutableMapping): |
|||
_cache: Any = ... |
|||
def __init__(self, *args: Any, **kwargs: Any) -> None: ... |
|||
@classmethod |
|||
def fromcache(cls, cache: Any, *args: Any, **kwargs: Any): ... |
|||
@property |
|||
def cache(self): ... |
|||
@property |
|||
def directory(self): ... |
|||
def __getitem__(self, key: Any): ... |
|||
def __setitem__(self, key: Any, value: Any) -> None: ... |
|||
def __delitem__(self, key: Any) -> None: ... |
|||
def setdefault(self, key: Any, default: Optional[Any] = ...): ... |
|||
def peekitem(self, last: bool = ...): ... |
|||
def pop(self, key: Any, default: Any = ...): ... |
|||
def popitem(self, last: bool = ...): ... |
|||
def push(self, value: Any, prefix: Optional[Any] = ..., side: str = ...): ... |
|||
def pull(self, prefix: Optional[Any] = ..., default: Any = ..., side: str = ...): ... |
|||
def clear(self) -> None: ... |
|||
def __iter__(self) -> Any: ... |
|||
def __reversed__(self): ... |
|||
def __len__(self): ... |
|||
def keys(self): ... |
|||
def values(self): ... |
|||
def items(self): ... |
|||
__hash__: Any = ... |
|||
def __getstate__(self): ... |
|||
def __setstate__(self, state: Any) -> None: ... |
|||
def __eq__(self, other: Any) -> Any: ... |
|||
def __ne__(self, other: Any) -> Any: ... |
|||
def memoize(self, name: Optional[Any] = ..., typed: bool = ...): ... |
|||
def transact(self) -> None: ... |
|||
def __repr__(self): ... |
@ -0,0 +1,51 @@ |
|||
from .core import ENOVAL as ENOVAL, args_to_key as args_to_key, full_name as full_name |
|||
from typing import Any, Optional |
|||
|
|||
class Averager: |
|||
_cache: Any = ... |
|||
_key: Any = ... |
|||
_expire: Any = ... |
|||
_tag: Any = ... |
|||
def __init__(self, cache: Any, key: Any, expire: Optional[Any] = ..., tag: Optional[Any] = ...) -> None: ... |
|||
def add(self, value: Any) -> None: ... |
|||
def get(self): ... |
|||
def pop(self): ... |
|||
|
|||
class Lock: |
|||
_cache: Any = ... |
|||
_key: Any = ... |
|||
_expire: Any = ... |
|||
_tag: Any = ... |
|||
def __init__(self, cache: Any, key: Any, expire: Optional[Any] = ..., tag: Optional[Any] = ...) -> None: ... |
|||
def acquire(self) -> None: ... |
|||
def release(self) -> None: ... |
|||
def locked(self): ... |
|||
def __enter__(self) -> None: ... |
|||
def __exit__(self, *exc_info: Any) -> None: ... |
|||
|
|||
class RLock: |
|||
_cache: Any = ... |
|||
_key: Any = ... |
|||
_expire: Any = ... |
|||
_tag: Any = ... |
|||
def __init__(self, cache: Any, key: Any, expire: Optional[Any] = ..., tag: Optional[Any] = ...) -> None: ... |
|||
def acquire(self) -> None: ... |
|||
def release(self) -> None: ... |
|||
def __enter__(self) -> None: ... |
|||
def __exit__(self, *exc_info: Any) -> None: ... |
|||
|
|||
class BoundedSemaphore: |
|||
_cache: Any = ... |
|||
_key: Any = ... |
|||
_value: Any = ... |
|||
_expire: Any = ... |
|||
_tag: Any = ... |
|||
def __init__(self, cache: Any, key: Any, value: int = ..., expire: Optional[Any] = ..., tag: Optional[Any] = ...) -> None: ... |
|||
def acquire(self) -> None: ... |
|||
def release(self) -> None: ... |
|||
def __enter__(self) -> None: ... |
|||
def __exit__(self, *exc_info: Any) -> None: ... |
|||
|
|||
def throttle(cache: Any, count: Any, seconds: Any, name: Optional[Any] = ..., expire: Optional[Any] = ..., tag: Optional[Any] = ..., time_func: Any = ..., sleep_func: Any = ...): ... |
|||
def barrier(cache: Any, lock_factory: Any, name: Optional[Any] = ..., expire: Optional[Any] = ..., tag: Optional[Any] = ...): ... |
|||
def memoize_stampede(cache: Any, expire: Any, name: Optional[Any] = ..., typed: bool = ..., tag: Optional[Any] = ..., beta: int = ...): ... |
@ -0,0 +1,156 @@ |
|||
# encoding:utf-8 |
|||
# author:Prinz23 |
|||
# project:imdb_api |
|||
|
|||
__author__ = 'Prinz23' |
|||
__version__ = '1.0' |
|||
__api_version__ = '1.0.0' |
|||
|
|||
import logging |
|||
import re |
|||
from .imdb_exceptions import * |
|||
from exceptions_helper import ex |
|||
from six import iteritems |
|||
from bs4_parser import BS4Parser |
|||
from lib import imdbpie |
|||
from lib.tvinfo_base.exceptions import BaseTVinfoShownotfound |
|||
from lib.tvinfo_base import TVInfoBase, TVINFO_TRAKT, TVINFO_TMDB, TVINFO_TVDB, TVINFO_TVRAGE, TVINFO_IMDB, \ |
|||
Person, PersonGenders, TVINFO_TWITTER, TVINFO_FACEBOOK, TVINFO_WIKIPEDIA, TVINFO_INSTAGRAM, Character, TVInfoShow |
|||
from sg_helpers import get_url, try_int |
|||
from lib.dateutil.parser import parser |
|||
|
|||
# noinspection PyUnreachableCode |
|||
if False: |
|||
from typing import Any, AnyStr, Dict, List, Optional, Union |
|||
from six import integer_types |
|||
|
|||
tz_p = parser() |
|||
log = logging.getLogger('imdb.api') |
|||
log.addHandler(logging.NullHandler()) |
|||
|
|||
|
|||
class IMDbIndexer(TVInfoBase): |
|||
# supported_id_searches = [TVINFO_IMDB] |
|||
supported_person_id_searches = [TVINFO_IMDB] |
|||
|
|||
# noinspection PyUnusedLocal |
|||
# noinspection PyDefaultArgument |
|||
def __init__(self, *args, **kwargs): |
|||
super(IMDbIndexer, self).__init__(*args, **kwargs) |
|||
|
|||
@staticmethod |
|||
def _convert_person(person_obj, filmography=None, bio=None): |
|||
if isinstance(person_obj, dict) and 'imdb_id' in person_obj: |
|||
imdb_id = try_int(re.search(r'(\d+)', person_obj['imdb_id']).group(1)) |
|||
return Person(p_id=imdb_id, name=person_obj['name'], ids={TVINFO_IMDB: imdb_id}) |
|||
characters = [] |
|||
for known_for in (filmography and filmography['filmography']) or []: |
|||
if known_for['titleType'] not in ('tvSeries', 'tvMiniSeries'): |
|||
continue |
|||
for character in known_for.get('characters') or []: |
|||
show = TVInfoShow() |
|||
show.id = try_int(re.search(r'(\d+)', known_for.get('id')).group(1)) |
|||
show.ids.imdb = show.id |
|||
show.seriesname = known_for.get('title') |
|||
show.firstaired = known_for.get('year') |
|||
characters.append( |
|||
Character(name=character, show=show, start_year=known_for.get('startYear'), |
|||
end_year=known_for.get('endYear')) |
|||
) |
|||
try: |
|||
birthdate = person_obj['base']['birthDate'] and tz_p.parse(person_obj['base']['birthDate']).date() |
|||
except (BaseException, Exception): |
|||
birthdate = None |
|||
try: |
|||
deathdate = person_obj['base']['deathDate'] and tz_p.parse(person_obj['base']['deathDate']).date() |
|||
except (BaseException, Exception): |
|||
deathdate = None |
|||
imdb_id = try_int(re.search(r'(\d+)', person_obj['id']).group(1)) |
|||
return Person(p_id=imdb_id, name=person_obj['base'].get('name'), ids={TVINFO_IMDB: imdb_id}, |
|||
gender=PersonGenders.imdb_map.get(person_obj['base'].get('gender'), PersonGenders.unknown), |
|||
image=person_obj['base'].get('image', {}).get('url'), |
|||
birthplace=person_obj['base'].get('birthPlace'), birthdate=birthdate, deathdate=deathdate, |
|||
height=person_obj['base'].get('heightCentimeters'), characters=characters, |
|||
deathplace=person_obj['base'].get('deathPlace'), |
|||
nicknames=set((person_obj['base'].get('nicknames') and person_obj['base'].get('nicknames')) |
|||
or []), |
|||
real_name=person_obj['base'].get('realName'), |
|||
akas=set((person_obj['base'].get('akas') and person_obj['base'].get('akas')) or []), bio=bio |
|||
) |
|||
|
|||
def _search_person(self, name=None, ids=None): |
|||
# type: (AnyStr, Dict[integer_types, integer_types]) -> List[Person] |
|||
""" |
|||
search for person by name |
|||
:param name: name to search for |
|||
:param ids: dict of ids to search |
|||
:return: list of found person's |
|||
""" |
|||
results, ids = [], ids or {} |
|||
for tv_src in self.supported_person_id_searches: |
|||
if tv_src in ids: |
|||
if TVINFO_IMDB == tv_src: |
|||
try: |
|||
p = self.get_person(ids[tv_src]) |
|||
except (BaseException, Exception): |
|||
p = None |
|||
if p: |
|||
results.append(p) |
|||
if name: |
|||
cache_name_key = 'p-name-%s' % name |
|||
is_none, ps = self._get_cache_entry(cache_name_key) |
|||
if None is ps and not is_none: |
|||
try: |
|||
ps = imdbpie.Imdb().search_for_name(name) |
|||
except (BaseException, Exception): |
|||
ps = None |
|||
self._set_cache_entry(cache_name_key, ps) |
|||
if ps: |
|||
for cp in ps: |
|||
if not any(1 for c in results if cp['imdb_id'] == 'nm%07d' % c.id): |
|||
results.append(self._convert_person(cp)) |
|||
return results |
|||
|
|||
def _get_bio(self, p_id): |
|||
try: |
|||
bio = get_url('https://www.imdb.com/name/nm%07d/bio' % p_id, headers={'Accept-Language': 'en'}) |
|||
if not bio: |
|||
return |
|||
with BS4Parser(bio) as bio_item: |
|||
bv = bio_item.find(string='Mini Bio', recursive=True).find_next('p') |
|||
for a in bv.findAll('a'): |
|||
a.replaceWithChildren() |
|||
for b in bv.findAll('br'): |
|||
b.replaceWith('\n') |
|||
return bv.get_text().strip() |
|||
except (BaseException, Exception): |
|||
return |
|||
|
|||
def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs): |
|||
# type: (integer_types, bool, bool, Any) -> Optional[Person] |
|||
if not p_id: |
|||
return |
|||
cache_main_key, cache_bio_key, cache_credits_key = 'p-main-%s' % p_id, 'p-bio-%s' % p_id, 'p-credits-%s' % p_id |
|||
is_none, p = self._get_cache_entry(cache_main_key) |
|||
if None is p and not is_none: |
|||
try: |
|||
p = imdbpie.Imdb().get_name(imdb_id='nm%07d' % p_id) |
|||
except (BaseException, Exception): |
|||
p = None |
|||
self._set_cache_entry(cache_main_key, p) |
|||
is_none, bio = self._get_cache_entry(cache_bio_key) |
|||
if None is bio and not is_none: |
|||
bio = self._get_bio(p_id) |
|||
self._set_cache_entry(cache_bio_key, bio) |
|||
fg = None |
|||
if get_show_credits: |
|||
is_none, fg = self._get_cache_entry(cache_credits_key) |
|||
if None is fg and not is_none: |
|||
try: |
|||
fg = imdbpie.Imdb().get_name_filmography(imdb_id='nm%07d' % p_id) |
|||
except (BaseException, Exception): |
|||
fg = None |
|||
self._set_cache_entry(cache_credits_key, fg) |
|||
if p: |
|||
return self._convert_person(p, filmography=fg, bio=bio) |
|||
|
@ -0,0 +1,62 @@ |
|||
# encoding:utf-8 |
|||
|
|||
"""Custom exceptions used or raised by tvmaze_api |
|||
""" |
|||
|
|||
__author__ = 'Prinz23' |
|||
__version__ = '1.0' |
|||
|
|||
__all__ = ['IMDbException', 'IMDbError', 'IMDbUserabort', 'IMDbShownotfound', |
|||
'IMDbSeasonnotfound', 'IMDbEpisodenotfound', 'IMDbAttributenotfound', 'IMDbTokenexpired'] |
|||
|
|||
from lib.tvinfo_base.exceptions import * |
|||
|
|||
|
|||
class IMDbException(BaseTVinfoException): |
|||
"""Any exception generated by tvdb_api |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class IMDbError(BaseTVinfoError, IMDbException): |
|||
"""An error with thetvdb.com (Cannot connect, for example) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class IMDbUserabort(BaseTVinfoUserabort, IMDbError): |
|||
"""User aborted the interactive selection (via |
|||
the q command, ^c etc) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class IMDbShownotfound(BaseTVinfoShownotfound, IMDbError): |
|||
"""Show cannot be found on thetvdb.com (non-existant show) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class IMDbSeasonnotfound(BaseTVinfoSeasonnotfound, IMDbError): |
|||
"""Season cannot be found on thetvdb.com |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class IMDbEpisodenotfound(BaseTVinfoEpisodenotfound, IMDbError): |
|||
"""Episode cannot be found on thetvdb.com |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class IMDbAttributenotfound(BaseTVinfoAttributenotfound, IMDbError): |
|||
"""Raised if an episode does not have the requested |
|||
attribute (such as a episode name) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class IMDbTokenexpired(BaseTVinfoAuthenticationerror, IMDbError): |
|||
"""token expired or missing thetvdb.com |
|||
""" |
|||
pass |
@ -0,0 +1,4 @@ |
|||
import logging |
|||
|
|||
logger = logging.getLogger('pytvmaze') |
|||
logger.addHandler(logging.NullHandler()) |
@ -0,0 +1,36 @@ |
|||
#!/usr/bin/python |
|||
|
|||
# TVMaze Free endpoints |
|||
show_search = 'https://api.tvmaze.com/search/shows?q={0}' |
|||
show_single_search = 'https://api.tvmaze.com/singlesearch/shows?q={0}' |
|||
lookup_tvrage = 'https://api.tvmaze.com/lookup/shows?tvrage={0}' |
|||
lookup_tvdb = 'https://api.tvmaze.com/lookup/shows?thetvdb={0}' |
|||
lookup_imdb = 'https://api.tvmaze.com/lookup/shows?imdb={0}' |
|||
get_schedule = 'https://api.tvmaze.com/schedule?country={0}&date={1}' |
|||
get_full_schedule = 'https://api.tvmaze.com/schedule/full' |
|||
show_main_info = 'https://api.tvmaze.com/shows/{0}' |
|||
episode_list = 'https://api.tvmaze.com/shows/{0}/episodes' |
|||
episode_by_number = 'https://api.tvmaze.com/shows/{0}/episodebynumber?season={1}&number={2}' |
|||
episodes_by_date = 'https://api.tvmaze.com/shows/{0}/episodesbydate?date={1}' |
|||
show_cast = 'https://api.tvmaze.com/shows/{0}/cast' |
|||
show_index = 'https://api.tvmaze.com/shows?page={0}' |
|||
people_search = 'https://api.tvmaze.com/search/people?q={0}' |
|||
person_main_info = 'https://api.tvmaze.com/people/{0}' |
|||
person_cast_credits = 'https://api.tvmaze.com/people/{0}/castcredits' |
|||
person_crew_credits = 'https://api.tvmaze.com/people/{0}/crewcredits' |
|||
show_crew = 'https://api.tvmaze.com/shows/{0}/crew' |
|||
show_updates = 'https://api.tvmaze.com/updates/shows' |
|||
show_akas = 'https://api.tvmaze.com/shows/{0}/akas' |
|||
show_seasons = 'https://api.tvmaze.com/shows/{0}/seasons' |
|||
season_by_id = 'https://api.tvmaze.com/seasons/{0}' |
|||
episode_by_id = 'https://api.tvmaze.com/episodes/{0}' |
|||
show_images = 'https://api.tvmaze.com/shows/{0}/images' |
|||
|
|||
# TVMaze Premium endpoints |
|||
followed_shows = 'https://api.tvmaze.com/v1/user/follows/shows{0}' |
|||
followed_people = 'https://api.tvmaze.com/v1/user/follows/people{0}' |
|||
followed_networks = 'https://api.tvmaze.com/v1/user/follows/networks{0}' |
|||
followed_web_channels = 'https://api.tvmaze.com/v1/user/follows/webchannels{0}' |
|||
marked_episodes = 'https://api.tvmaze.com/v1/user/episodes{0}' |
|||
voted_shows = 'https://api.tvmaze.com/v1/user/votes/shows{0}' |
|||
voted_episodes = 'https://api.tvmaze.com/v1/user/votes/episodes{0}' |
@ -0,0 +1,166 @@ |
|||
import sys |
|||
from . import logger |
|||
|
|||
|
|||
class BaseError(Exception): |
|||
def __init__(self, value): |
|||
self.value = value |
|||
logger.error(self.__str__()) |
|||
|
|||
def __str__(self): |
|||
if sys.version_info > (3,): |
|||
return self.value |
|||
else: |
|||
return unicode(self.value).encode('utf-8') |
|||
|
|||
|
|||
class ShowNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class IDNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class ScheduleNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class EpisodeNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NoEpisodesForAirdate(BaseError): |
|||
pass |
|||
|
|||
|
|||
class CastNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class ShowIndexError(BaseError): |
|||
pass |
|||
|
|||
|
|||
class PersonNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class CreditsNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class UpdateNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class AKASNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class SeasonNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class GeneralError(BaseError): |
|||
pass |
|||
|
|||
|
|||
class MissingParameters(BaseError): |
|||
pass |
|||
|
|||
|
|||
class IllegalAirDate(BaseError): |
|||
pass |
|||
|
|||
|
|||
class ConnectionError(BaseError): |
|||
pass |
|||
|
|||
|
|||
class BadRequest(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NoFollowedShows(BaseError): |
|||
pass |
|||
|
|||
|
|||
class ShowNotFollowed(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NoFollowedPeople(BaseError): |
|||
pass |
|||
|
|||
|
|||
class PersonNotFollowed(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NoMarkedEpisodes(BaseError): |
|||
pass |
|||
|
|||
|
|||
class EpisodeNotMarked(BaseError): |
|||
pass |
|||
|
|||
|
|||
class InvalidMarkedEpisodeType(BaseError): |
|||
pass |
|||
|
|||
|
|||
class InvalidEmbedValue(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NetworkNotFollowed(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NoFollowedWebChannels(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NoVotedShows(BaseError): |
|||
pass |
|||
|
|||
|
|||
class ShowNotVotedFor(BaseError): |
|||
pass |
|||
|
|||
|
|||
class InvalidVoteValue(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NoVotedEpisodes(BaseError): |
|||
pass |
|||
|
|||
|
|||
class EpisodeNotVotedFor(BaseError): |
|||
pass |
|||
|
|||
|
|||
class CrewNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class ShowImagesNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NoFollowedNetworks(BaseError): |
|||
pass |
|||
|
|||
|
|||
class NetworkNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class WebChannelNotFound(BaseError): |
|||
pass |
|||
|
|||
|
|||
class WebChannelNotFollowed(BaseError): |
|||
pass |
File diff suppressed because it is too large
@ -0,0 +1,348 @@ |
|||
# encoding:utf-8 |
|||
# author:Prinz23 |
|||
# project:tmdb_api |
|||
|
|||
__author__ = 'Prinz23' |
|||
__version__ = '1.0' |
|||
__api_version__ = '1.0.0' |
|||
|
|||
import json |
|||
import logging |
|||
import datetime |
|||
|
|||
from six import iteritems |
|||
from sg_helpers import get_tmdb_info, get_url, try_int |
|||
from lib.dateutil.parser import parser |
|||
from lib.dateutil.tz.tz import _datetime_to_timestamp |
|||
from lib.exceptions_helper import ConnectionSkipException, ex |
|||
from .tmdb_exceptions import * |
|||
from lib.tvinfo_base import TVInfoBase, TVInfoImage, TVInfoImageSize, TVInfoImageType, Character, Crew, \ |
|||
crew_type_names, Person, RoleTypes, TVInfoEpisode, TVInfoIDs, TVInfoSeason, PersonGenders, TVINFO_TVMAZE, \ |
|||
TVINFO_TVDB, TVINFO_IMDB, TVINFO_TMDB, TVINFO_TWITTER, TVINFO_INSTAGRAM, TVINFO_FACEBOOK, TVInfoShow |
|||
from lib import tmdbsimple |
|||
|
|||
# noinspection PyUnreachableCode |
|||
if False: |
|||
from typing import Any, AnyStr, Dict, List, Optional, Union |
|||
from six import integer_types |
|||
|
|||
log = logging.getLogger('tmdb.api') |
|||
log.addHandler(logging.NullHandler()) |
|||
tz_p = parser() |
|||
tmdbsimple.API_KEY = 'edc5f123313769de83a71e157758030b' |
|||
|
|||
id_map = {TVINFO_IMDB: 'imdb_id', TVINFO_TVDB: 'tvdb_id', TVINFO_FACEBOOK: 'facebook_id', TVINFO_TWITTER: 'twitter_id', |
|||
TVINFO_INSTAGRAM: 'instagram_id'} |
|||
|
|||
|
|||
def tmdb_GET(self, path, params=None): |
|||
url = self._get_complete_url(path) |
|||
params = self._get_params(params) |
|||
return get_url(url=url, params=params, json=True, raise_skip_exception=True) |
|||
|
|||
|
|||
def tmdb_POST(self, path, params=None, payload=None): |
|||
url = self._get_complete_url(path) |
|||
params = self._get_params(params) |
|||
data = json.dumps(payload) if payload else payload |
|||
return get_url(url=url, params=params, post_data=data, json=True, raise_skip_exception=True) |
|||
|
|||
|
|||
tmdbsimple.base.TMDB._GET = tmdb_GET |
|||
tmdbsimple.base.TMDB._POST = tmdb_POST |
|||
|
|||
|
|||
class TmdbIndexer(TVInfoBase): |
|||
API_KEY = tmdbsimple.API_KEY |
|||
# supported_id_searches = [TVINFO_TVDB, TVINFO_IMDB, TVINFO_TMDB, TVINFO_TRAKT] |
|||
supported_person_id_searches = [TVINFO_TMDB, TVINFO_IMDB, TVINFO_TWITTER, TVINFO_INSTAGRAM, TVINFO_FACEBOOK] |
|||
|
|||
# noinspection PyUnusedLocal |
|||
# noinspection PyDefaultArgument |
|||
def __init__(self, *args, **kwargs): |
|||
super(TmdbIndexer, self).__init__(*args, **kwargs) |
|||
response = get_tmdb_info() |
|||
self.img_base_url = response['images']['base_url'] |
|||
self.img_sizes = response['images']['profile_sizes'] |
|||
self.tv_genres = get_tmdb_info(get_tv_genres=True) |
|||
|
|||
def _convert_person_obj(self, person_obj): |
|||
gender = PersonGenders.tmdb_map.get(person_obj.get('gender'), PersonGenders.unknown) |
|||
try: |
|||
birthdate = person_obj.get('birthday') and tz_p.parse(person_obj.get('birthday')).date() |
|||
except (BaseException, Exception): |
|||
birthdate = None |
|||
try: |
|||
deathdate = person_obj.get('deathday') and tz_p.parse(person_obj.get('deathday')).date() |
|||
except (BaseException, Exception): |
|||
deathdate = None |
|||
|
|||
cast = person_obj.get('cast') or person_obj.get('tv_credits', {}).get('cast') |
|||
|
|||
characters = [] |
|||
for character in cast or []: |
|||
show = TVInfoShow() |
|||
show.id = character.get('id') |
|||
show.ids = TVInfoIDs(ids={TVINFO_TMDB: show.id}) |
|||
show.seriesname = character.get('original_name') |
|||
show.overview = character.get('overview') |
|||
show.firstaired = character.get('first_air_date') |
|||
characters.append( |
|||
Character(name=character.get('character'), show=show) |
|||
) |
|||
|
|||
pi = person_obj.get('images') |
|||
image_url, thumb_url = None, None |
|||
if pi: |
|||
def size_str_to_int(x): |
|||
return float('inf') if 'original' == x else int(x[1:]) |
|||
|
|||
thumb_size = next(s for s in sorted(self.img_sizes, key=size_str_to_int) |
|||
if 150 < size_str_to_int(s)) |
|||
for i in sorted(pi['profiles'], key=lambda a: a['vote_average'] or 0, reverse=True): |
|||
if 500 < i['height'] and not image_url: |
|||
image_url = '%s%s%s' % (self.img_base_url, 'original', i['file_path']) |
|||
thumb_url = '%s%s%s' % (self.img_base_url, thumb_size, i['file_path']) |
|||
elif not thumb_url: |
|||
thumb_url = '%s%s%s' % (self.img_base_url, 'original', i['file_path']) |
|||
if image_url and thumb_url: |
|||
break |
|||
|
|||
return Person(p_id=person_obj.get('id'), gender=gender, name=person_obj.get('name'), birthdate=birthdate, |
|||
deathdate=deathdate, bio=person_obj.get('biography'), birthplace=person_obj.get('place_of_birth'), |
|||
homepage=person_obj.get('homepage'), characters=characters, image=image_url, thumb_url=thumb_url, |
|||
ids={TVINFO_TMDB: person_obj.get('id'), |
|||
TVINFO_IMDB: |
|||
person_obj.get('imdb_id') and try_int(person_obj['imdb_id'].replace('nm', ''), None)}) |
|||
|
|||
def _search_person(self, name=None, ids=None): |
|||
# type: (AnyStr, Dict[integer_types, integer_types]) -> List[Person] |
|||
""" |
|||
search for person by name |
|||
:param name: name to search for |
|||
:param ids: dict of ids to search |
|||
:return: list of found person's |
|||
""" |
|||
results, ids = [], ids or {} |
|||
search_text_obj = tmdbsimple.Search() |
|||
for tv_src in self.supported_person_id_searches: |
|||
if tv_src in ids: |
|||
if TVINFO_TMDB == tv_src: |
|||
try: |
|||
people_obj = self.get_person(ids[tv_src]) |
|||
except ConnectionSkipException as e: |
|||
raise e |
|||
except (BaseException, Exception): |
|||
people_obj = None |
|||
if people_obj and not any(1 for r in results if r.id == people_obj.id): |
|||
results.append(people_obj) |
|||
elif tv_src in (TVINFO_IMDB, TVINFO_TMDB): |
|||
try: |
|||
cache_key_name = 'p-src-%s-%s' % (tv_src, ids.get(tv_src)) |
|||
is_none, result_objs = self._get_cache_entry(cache_key_name) |
|||
if None is result_objs and not is_none: |
|||
result_objs = tmdbsimple.Find(id=(ids.get(tv_src), |
|||
'nm%07d' % ids.get(tv_src))[TVINFO_IMDB == tv_src]).info( |
|||
external_source=id_map[tv_src]).get('person_results') |
|||
self._set_cache_entry(cache_key_name, result_objs) |
|||
except ConnectionSkipException as e: |
|||
raise e |
|||
except (BaseException, Exception): |
|||
result_objs = None |
|||
if result_objs: |
|||
for person_obj in result_objs: |
|||
if not any(1 for r in results if r.id == person_obj['id']): |
|||
results.append(self._convert_person_obj(person_obj)) |
|||
else: |
|||
continue |
|||
if name: |
|||
cache_key_name = 'p-src-text-%s' % name |
|||
is_none, people_objs = self._get_cache_entry(cache_key_name) |
|||
if None is people_objs and not is_none: |
|||
try: |
|||
people_objs = search_text_obj.person(query=name, include_adult=True) |
|||
self._set_cache_entry(cache_key_name, people_objs) |
|||
except ConnectionSkipException as e: |
|||
raise e |
|||
except (BaseException, Exception): |
|||
people_objs = None |
|||
if people_objs and people_objs.get('results'): |
|||
for person_obj in people_objs['results']: |
|||
if not any(1 for r in results if r.id == person_obj['id']): |
|||
results.append(self._convert_person_obj(person_obj)) |
|||
|
|||
return results |
|||
|
|||
def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs): |
|||
# type: (integer_types, bool, bool, Any) -> Optional[Person] |
|||
kw = {} |
|||
to_append = [] |
|||
if get_show_credits: |
|||
to_append.append('tv_credits') |
|||
if get_images: |
|||
to_append.append('images') |
|||
if to_append: |
|||
kw['append_to_response'] = ','.join(to_append) |
|||
|
|||
cache_key_name = 'p-%s-%s' % (p_id, '-'.join(to_append)) |
|||
is_none, people_obj = self._get_cache_entry(cache_key_name) |
|||
if None is people_obj and not is_none: |
|||
try: |
|||
people_obj = tmdbsimple.People(id=p_id).info(**kw) |
|||
except ConnectionSkipException as e: |
|||
raise e |
|||
except (BaseException, Exception): |
|||
people_obj = None |
|||
self._set_cache_entry(cache_key_name, people_obj) |
|||
|
|||
if people_obj: |
|||
return self._convert_person_obj(people_obj) |
|||
|
|||
def _convert_show(self, show_dict): |
|||
# type: (Dict) -> TVInfoShow |
|||
tv_s = TVInfoShow() |
|||
if show_dict: |
|||
tv_s.seriesname = show_dict.get('name') or show_dict.get('original_name') or show_dict.get('original_title') |
|||
org_title = show_dict.get('original_name') or show_dict.get('original_title') |
|||
if org_title != tv_s.seriesname: |
|||
tv_s.aliases = [org_title] |
|||
tv_s.id = show_dict.get('id') |
|||
tv_s.seriesid = tv_s.id |
|||
tv_s.language = show_dict.get('original_language') |
|||
tv_s.overview = show_dict.get('overview') |
|||
tv_s.firstaired = show_dict.get('first_air_date') |
|||
tv_s.vote_count = show_dict.get('vote_count') |
|||
tv_s.vote_average = show_dict.get('vote_average') |
|||
tv_s.popularity = show_dict.get('popularity') |
|||
tv_s.origin_countries = show_dict.get('origin_country') or [] |
|||
tv_s.genre_list = [] |
|||
for g in show_dict.get('genre_ids') or []: |
|||
if g in self.tv_genres: |
|||
tv_s.genre_list.append(self.tv_genres.get(g)) |
|||
tv_s.genre = ', '.join(tv_s.genre_list) |
|||
image_url = show_dict.get('poster_path') and '%s%s%s' % (self.img_base_url, 'original', |
|||
show_dict.get('poster_path')) |
|||
backdrop_url = show_dict.get('backdrop_path') and '%s%s%s' % (self.img_base_url, 'original', |
|||
show_dict.get('backdrop_path')) |
|||
tv_s.poster = image_url |
|||
tv_s.fanart = backdrop_url |
|||
tv_s.ids = TVInfoIDs(tmdb=tv_s.id) |
|||
return tv_s |
|||
|
|||
def _get_show_list(self, src_method, result_count, **kwargs): |
|||
result = [] |
|||
try: |
|||
c_page = 1 |
|||
while len(result) < result_count: |
|||
results = src_method(page=c_page, **kwargs) |
|||
t_pages = results.get('total_pages') |
|||
if c_page != results.get('page') or c_page >= t_pages: |
|||
break |
|||
c_page += 1 |
|||
if results and 'results' in results: |
|||
result += [self._convert_show(t) for t in results['results']] |
|||
else: |
|||
break |
|||
except (BaseException, Exception): |
|||
pass |
|||
return result[:result_count] |
|||
|
|||
def get_trending(self, result_count=100, time_window='day', **kwargs): |
|||
""" |
|||
list of trending tv shows for day or week |
|||
:param result_count: |
|||
:param time_window: valid values: 'day', 'week' |
|||
""" |
|||
t_windows = ('day', 'week')['week' == time_window] |
|||
return self._get_show_list(tmdbsimple.Trending(media_type='tv', time_window=t_windows).info, result_count) |
|||
|
|||
def get_popular(self, result_count=100, **kwargs): |
|||
return self._get_show_list(tmdbsimple.TV().popular, result_count) |
|||
|
|||
def get_top_rated(self, result_count=100, **kwargs): |
|||
return self._get_show_list(tmdbsimple.TV().top_rated, result_count) |
|||
|
|||
def discover(self, result_count=100, **kwargs): |
|||
""" |
|||
Discover TV shows by different types of data like average rating, |
|||
number of votes, genres, the network they aired on and air dates. |
|||
|
|||
Discover also supports a nice list of sort options. See below for all |
|||
of the available options. |
|||
|
|||
Also note that a number of filters support being comma (,) or pipe (|) |
|||
separated. Comma's are treated like an AND and query while pipe's are |
|||
an OR. |
|||
|
|||
Some examples of what can be done with discover can be found at |
|||
https://www.themoviedb.org/documentation/api/discover. |
|||
|
|||
kwargs: |
|||
language: (optional) ISO 639-1 code. |
|||
sort_by: (optional) Available options are 'vote_average.desc', |
|||
'vote_average.asc', 'first_air_date.desc', |
|||
'first_air_date.asc', 'popularity.desc', 'popularity.asc' |
|||
sort_by: (optional) Allowed values: vote_average.desc, |
|||
vote_average.asc, first_air_date.desc, first_air_date.asc, |
|||
popularity.desc, popularity.asc |
|||
Default: popularity.desc |
|||
air_date.gte: (optional) Filter and only include TV shows that have |
|||
a air date (by looking at all episodes) that is greater or |
|||
equal to the specified value. |
|||
air_date.lte: (optional) Filter and only include TV shows that have |
|||
a air date (by looking at all episodes) that is less than or |
|||
equal to the specified value. |
|||
first_air_date.gte: (optional) Filter and only include TV shows |
|||
that have a original air date that is greater or equal to the |
|||
specified value. Can be used in conjunction with the |
|||
"include_null_first_air_dates" filter if you want to include |
|||
items with no air date. |
|||
first_air_date.lte: (optional) Filter and only include TV shows |
|||
that have a original air date that is less than or equal to the |
|||
specified value. Can be used in conjunction with the |
|||
"include_null_first_air_dates" filter if you want to include |
|||
items with no air date. |
|||
first_air_date_year: (optional) Filter and only include TV shows |
|||
that have a original air date year that equal to the specified |
|||
value. Can be used in conjunction with the |
|||
"include_null_first_air_dates" filter if you want to include |
|||
items with no air date. |
|||
timezone: (optional) Used in conjunction with the air_date.gte/lte |
|||
filter to calculate the proper UTC offset. Default |
|||
America/New_York. |
|||
vote_average.gte: (optional) Filter and only include movies that |
|||
have a rating that is greater or equal to the specified value. |
|||
Minimum 0. |
|||
vote_count.gte: (optional) Filter and only include movies that have |
|||
a rating that is less than or equal to the specified value. |
|||
Minimum 0. |
|||
with_genres: (optional) Comma separated value of genre ids that you |
|||
want to include in the results. |
|||
with_networks: (optional) Comma separated value of network ids that |
|||
you want to include in the results. |
|||
without_genres: (optional) Comma separated value of genre ids that |
|||
you want to exclude from the results. |
|||
with_runtime.gte: (optional) Filter and only include TV shows with |
|||
an episode runtime that is greater than or equal to a value. |
|||
with_runtime.lte: (optional) Filter and only include TV shows with |
|||
an episode runtime that is less than or equal to a value. |
|||
include_null_first_air_dates: (optional) Use this filter to include |
|||
TV shows that don't have an air date while using any of the |
|||
"first_air_date" filters. |
|||
with_original_language: (optional) Specify an ISO 639-1 string to |
|||
filter results by their original language value. |
|||
without_keywords: (optional) Exclude items with certain keywords. |
|||
You can comma and pipe seperate these values to create an 'AND' |
|||
or 'OR' logic. |
|||
screened_theatrically: (optional) Filter results to include items |
|||
that have been screened theatrically. |
|||
with_companies: (optional) A comma separated list of production |
|||
company ID's. Only include movies that have one of the ID's |
|||
added as a production company. |
|||
with_keywords: (optional) A comma separated list of keyword ID's. |
|||
Only includes TV shows that have one of the ID's added as a |
|||
keyword. |
|||
|
|||
:param result_count: |
|||
""" |
|||
return self._get_show_list(tmdbsimple.Discover().tv, result_count, **kwargs) |
@ -0,0 +1,62 @@ |
|||
# encoding:utf-8 |
|||
|
|||
"""Custom exceptions used or raised by tmdb_api |
|||
""" |
|||
|
|||
__author__ = 'Prinz23' |
|||
__version__ = '1.0' |
|||
|
|||
__all__ = ['TmdbException', 'TmdbError', 'TmdbUserabort', 'TmdbShownotfound', |
|||
'TmdbSeasonnotfound', 'TmdbEpisodenotfound', 'TmdbAttributenotfound', 'TmdbTokenexpired'] |
|||
|
|||
from lib.tvinfo_base.exceptions import * |
|||
|
|||
|
|||
class TmdbException(BaseTVinfoException): |
|||
"""Any exception generated by tvdb_api |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TmdbError(BaseTVinfoError, TmdbException): |
|||
"""An error with thetvdb.com (Cannot connect, for example) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TmdbUserabort(BaseTVinfoUserabort, TmdbError): |
|||
"""User aborted the interactive selection (via |
|||
the q command, ^c etc) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TmdbShownotfound(BaseTVinfoShownotfound, TmdbError): |
|||
"""Show cannot be found on thetvdb.com (non-existant show) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TmdbSeasonnotfound(BaseTVinfoSeasonnotfound, TmdbError): |
|||
"""Season cannot be found on thetvdb.com |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TmdbEpisodenotfound(BaseTVinfoEpisodenotfound, TmdbError): |
|||
"""Episode cannot be found on thetvdb.com |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TmdbAttributenotfound(BaseTVinfoAttributenotfound, TmdbError): |
|||
"""Raised if an episode does not have the requested |
|||
attribute (such as a episode name) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TmdbTokenexpired(BaseTVinfoAuthenticationerror, TmdbError): |
|||
"""token expired or missing thetvdb.com |
|||
""" |
|||
pass |
@ -0,0 +1,503 @@ |
|||
# encoding:utf-8 |
|||
# author:Prinz23 |
|||
# project:tvmaze_api |
|||
|
|||
__author__ = 'Prinz23' |
|||
__version__ = '1.0' |
|||
__api_version__ = '1.0.0' |
|||
|
|||
import logging |
|||
import datetime |
|||
import requests |
|||
from requests.packages.urllib3.util.retry import Retry |
|||
from requests.adapters import HTTPAdapter |
|||
|
|||
from six import iteritems |
|||
from sg_helpers import get_url, try_int |
|||
from lib.dateutil.parser import parser |
|||
from lib.dateutil.tz.tz import _datetime_to_timestamp |
|||
from lib.exceptions_helper import ConnectionSkipException, ex |
|||
from .tvmaze_exceptions import * |
|||
from lib.tvinfo_base import TVInfoBase, TVInfoImage, TVInfoImageSize, TVInfoImageType, Character, Crew, \ |
|||
crew_type_names, Person, RoleTypes, TVInfoShow, TVInfoEpisode, TVInfoIDs, TVInfoSeason, PersonGenders, \ |
|||
TVINFO_TVMAZE, TVINFO_TVDB, TVINFO_IMDB |
|||
from lib.pytvmaze import tvmaze |
|||
|
|||
# noinspection PyUnreachableCode |
|||
if False: |
|||
from typing import Any, AnyStr, Dict, List, Optional, Union |
|||
from six import integer_types |
|||
|
|||
log = logging.getLogger('tvmaze.api') |
|||
log.addHandler(logging.NullHandler()) |
|||
|
|||
|
|||
# Query TVMaze free endpoints |
|||
def tvmaze_endpoint_standard_get(url): |
|||
s = requests.Session() |
|||
retries = Retry(total=5, |
|||
backoff_factor=0.1, |
|||
status_forcelist=[429]) |
|||
s.mount('http://', HTTPAdapter(max_retries=retries)) |
|||
s.mount('https://', HTTPAdapter(max_retries=retries)) |
|||
return get_url(url, json=True, session=s, hooks={'response': tvmaze._record_hook}, raise_skip_exception=True) |
|||
|
|||
|
|||
tvmaze.TVMaze.endpoint_standard_get = staticmethod(tvmaze_endpoint_standard_get) |
|||
tvm_obj = tvmaze.TVMaze() |
|||
empty_ep = TVInfoEpisode() |
|||
empty_se = TVInfoSeason() |
|||
tz_p = parser() |
|||
|
|||
img_type_map = { |
|||
'poster': TVInfoImageType.poster, |
|||
'banner': TVInfoImageType.banner, |
|||
'background': TVInfoImageType.fanart, |
|||
'typography': TVInfoImageType.typography, |
|||
} |
|||
|
|||
img_size_map = { |
|||
'original': TVInfoImageSize.original, |
|||
'medium': TVInfoImageSize.medium, |
|||
} |
|||
|
|||
show_map = { |
|||
'id': 'maze_id', |
|||
'ids': 'externals', |
|||
# 'slug': '', |
|||
'seriesid': 'maze_id', |
|||
'seriesname': 'name', |
|||
'aliases': 'akas', |
|||
# 'season': '', |
|||
'classification': 'type', |
|||
# 'genre': '', |
|||
'genre_list': 'genres', |
|||
# 'actors': '', |
|||
# 'cast': '', |
|||
# 'show_type': '', |
|||
# 'network': 'network', |
|||
# 'network_id': '', |
|||
# 'network_timezone': '', |
|||
# 'network_country': '', |
|||
# 'network_country_code': '', |
|||
# 'network_is_stream': '', |
|||
'runtime': 'runtime', |
|||
'language': 'language', |
|||
'official_site': 'official_site', |
|||
# 'imdb_id': '', |
|||
# 'zap2itid': '', |
|||
# 'airs_dayofweek': '', |
|||
# 'airs_time': '', |
|||
# 'time': '', |
|||
'firstaired': 'premiered', |
|||
# 'added': '', |
|||
# 'addedby': '', |
|||
# 'siteratingcount': '', |
|||
# 'lastupdated': '', |
|||
# 'contentrating': '', |
|||
'rating': 'rating', |
|||
'status': 'status', |
|||
'overview': 'summary', |
|||
# 'poster': 'image', |
|||
# 'poster_thumb': '', |
|||
# 'banner': '', |
|||
# 'banner_thumb': '', |
|||
# 'fanart': '', |
|||
# 'banners': '', |
|||
'updated_timestamp': 'updated', |
|||
} |
|||
season_map = { |
|||
'id': 'id', |
|||
'number': 'season_number', |
|||
'name': 'name', |
|||
# 'actors': '', |
|||
# 'cast': '', |
|||
# 'network': '', |
|||
# 'network_id': '', |
|||
# 'network_timezone': '', |
|||
# 'network_country': '', |
|||
# 'network_country_code': '', |
|||
# 'network_is_stream': '', |
|||
'ordered': '', |
|||
'start_date': 'premiere_date', |
|||
'end_date': 'end_date', |
|||
# 'poster': '', |
|||
'summery': 'summary', |
|||
'episode_order': 'episode_order', |
|||
} |
|||
|
|||
|
|||
class TvMaze(TVInfoBase): |
|||
supported_id_searches = [TVINFO_TVMAZE, TVINFO_TVDB, TVINFO_IMDB] |
|||
supported_person_id_searches = [TVINFO_TVMAZE] |
|||
|
|||
def __init__(self, *args, **kwargs): |
|||
super(TvMaze, self).__init__(*args, **kwargs) |
|||
|
|||
def _search_show(self, name=None, ids=None, **kwargs): |
|||
def _make_result_dict(s): |
|||
return {'seriesname': s.name, 'id': s.id, 'firstaired': s.premiered, |
|||
'network': s.network and s.network.name, |
|||
'genres': s.genres, 'overview': s.summary, |
|||
'aliases': [a.name for a in s.akas], 'image': s.image and s.image.get('original'), |
|||
'ids': TVInfoIDs(tvdb=s.externals.get('thetvdb'), rage=s.externals.get('tvrage'), tvmaze=s.id, |
|||
imdb=s.externals.get('imdb') and try_int(s.externals.get('imdb').replace('tt', ''), |
|||
None))} |
|||
results = [] |
|||
if ids: |
|||
for t, p in iteritems(ids): |
|||
if t in self.supported_id_searches: |
|||
if t == TVINFO_TVDB: |
|||
try: |
|||
show = tvmaze.lookup_tvdb(p) |
|||
except (BaseException, Exception): |
|||
continue |
|||
elif t == TVINFO_IMDB: |
|||
try: |
|||
show = tvmaze.lookup_imdb((p, 'tt%07d' % p)[not str(p).startswith('tt')]) |
|||
except (BaseException, Exception): |
|||
continue |
|||
elif t == TVINFO_TVMAZE: |
|||
try: |
|||
show = tvm_obj.get_show(maze_id=p) |
|||
except (BaseException, Exception): |
|||
continue |
|||
else: |
|||
continue |
|||
if show: |
|||
try: |
|||
if show.id not in [i['id'] for i in results]: |
|||
results.append(_make_result_dict(show)) |
|||
except (BaseException, Exception) as e: |
|||
log.debug('Error creating result dict: %s' % ex(e)) |
|||
if name: |
|||
for n in ([name], name)[isinstance(name, list)]: |
|||
try: |
|||
shows = tvmaze.show_search(n) |
|||
results = [_make_result_dict(s) for s in shows] |
|||
except (BaseException, Exception) as e: |
|||
log.debug('Error searching for show: %s' % ex(e)) |
|||
return results |
|||
|
|||
def _set_episode(self, sid, ep_obj): |
|||
for _k, _s in [('seasonnumber', 'season_number'), ('episodenumber', 'episode_number'), |
|||
('episodename', 'title'), ('overview', 'summary'), ('firstaired', 'airdate'), |
|||
('airtime', 'airtime'), ('runtime', 'runtime'), |
|||
('seriesid', 'maze_id'), ('id', 'maze_id'), ('is_special', 'special'), |
|||
('filename', 'image')]: |
|||
if 'filename' == _k: |
|||
image = getattr(ep_obj, _s, {}) or {} |
|||
image = image.get('original') or image.get('medium') |
|||
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number, _k, image) |
|||
else: |
|||
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number, _k, |
|||
getattr(ep_obj, _s, getattr(empty_ep, _k))) |
|||
|
|||
if ep_obj.airstamp: |
|||
try: |
|||
at = _datetime_to_timestamp(tz_p.parse(ep_obj.airstamp)) |
|||
self._set_item(sid, ep_obj.season_number, ep_obj.episode_number, 'timestamp', at) |
|||
except (BaseException, Exception): |
|||
pass |
|||
|
|||
def _set_network(self, show_obj, network, is_stream): |
|||
show_obj['network'] = network.name |
|||
show_obj['network_timezone'] = network.timezone |
|||
show_obj['network_country'] = network.country |
|||
show_obj['network_country_code'] = network.code |
|||
show_obj['network_id'] = network.maze_id |
|||
show_obj['network_is_stream'] = is_stream |
|||
|
|||
def _get_show_data(self, sid, language, get_ep_info=False, banners=False, posters=False, seasons=False, |
|||
seasonwides=False, fanart=False, actors=False, **kwargs): |
|||
log.debug('Getting all series data for %s' % sid) |
|||
try: |
|||
self.show_not_found = False |
|||
show_data = tvm_obj.get_show(maze_id=sid, embed='cast%s' % ('', ',episodes')[get_ep_info]) |
|||
except tvmaze.ShowNotFound: |
|||
self.show_not_found = True |
|||
return False |
|||
except (BaseException, Exception) as e: |
|||
log.debug('Error getting data for tvmaze show id: %s' % sid) |
|||
return False |
|||
|
|||
show_obj = self.shows[sid].__dict__ |
|||
for k, v in iteritems(show_obj): |
|||
if k not in ('cast', 'crew', 'images'): |
|||
show_obj[k] = getattr(show_data, show_map.get(k, k), show_obj[k]) |
|||
if show_data.image: |
|||
show_obj['poster'] = show_data.image.get('original') |
|||
show_obj['poster_thumb'] = show_data.image.get('medium') |
|||
|
|||
if (banners or posters or fanart or |
|||
any(self.config.get('%s_enabled' % t, False) for t in ('banners', 'posters', 'fanart'))) and \ |
|||
not all(getattr(self.shows[sid], '%s_loaded' % t, False) for t in ('poster', 'banner', 'fanart')): |
|||
if show_data.images: |
|||
b_set, f_set = False, False |
|||
self.shows[sid].poster_loaded = True |
|||
self.shows[sid].banner_loaded = True |
|||
self.shows[sid].fanart_loaded = True |
|||
for img in show_data.images: |
|||
img_type = img_type_map.get(img.type, TVInfoImageType.other) |
|||
img_src = {} |
|||
for res, img_url in iteritems(img.resolutions): |
|||
img_size = img_size_map.get(res) |
|||
if img_size: |
|||
img_src[img_size] = img_url.get('url') |
|||
show_obj['images'].setdefault(img_type, []).append( |
|||
TVInfoImage(image_type=img_type, sizes=img_src, img_id=img.id, main_image=img.main, |
|||
type_str=img.type)) |
|||
if not b_set and 'banner' == img.type: |
|||
b_set = True |
|||
show_obj['banner'] = img.resolutions.get('original')['url'] |
|||
show_obj['banner_thumb'] = img.resolutions.get('medium')['url'] |
|||
elif not f_set and 'background' == img.type: |
|||
f_set = True |
|||
show_obj['fanart'] = img.resolutions.get('original')['url'] |
|||
|
|||
if show_data.schedule: |
|||
if 'time' in show_data.schedule: |
|||
show_obj['airs_time'] = show_data.schedule['time'] |
|||
try: |
|||
h, m = show_data.schedule['time'].split(':') |
|||
h, m = try_int(h, None), try_int(m, None) |
|||
if None is not h and None is not m: |
|||
show_obj['time'] = datetime.time(hour=h, minute=m) |
|||
except (BaseException, Exception): |
|||
pass |
|||
if 'days' in show_data.schedule: |
|||
show_obj['airs_dayofweek'] = ', '.join(show_data.schedule['days']) |
|||
if show_data.genres: |
|||
show_obj['genre'] = ','.join(show_data.genres) |
|||
|
|||
if (actors or self.config['actors_enabled']) and not getattr(self.shows.get(sid), 'actors_loaded', False): |
|||
if show_data.cast: |
|||
character_person_ids = {} |
|||
for ch in show_obj['cast'][RoleTypes.ActorMain]: |
|||
character_person_ids.setdefault(ch.id, []).extend([p.id for p in ch.person]) |
|||
for ch in show_data.cast.characters: |
|||
existing_character = next((c for c in show_obj['cast'][RoleTypes.ActorMain] if c.id == ch.id), |
|||
None) # type: Optional[Character] |
|||
person = self._convert_person(ch.person) |
|||
if existing_character: |
|||
existing_person = next((p for p in existing_character.person |
|||
if person.id == p.ids.get(TVINFO_TVMAZE)), |
|||
None) # type: Person |
|||
if existing_person: |
|||
try: |
|||
character_person_ids[ch.id].remove(existing_person.id) |
|||
except (BaseException, Exception): |
|||
print('error') |
|||
pass |
|||
existing_person.p_id, existing_person.name, existing_person.image, existing_person.gender, \ |
|||
existing_person.birthdate, existing_person.deathdate, existing_person.country, \ |
|||
existing_person.country_code, existing_person.country_timezone, existing_person.thumb_url, \ |
|||
existing_person.url, existing_person.ids = \ |
|||
ch.person.id, ch.person.name, ch.person.image and ch.person.image.get('original'), \ |
|||
PersonGenders.named.get(ch.person.gender and ch.person.gender.lower(), |
|||
PersonGenders.unknown),\ |
|||
person.birthdate, person.deathdate,\ |
|||
ch.person.country and ch.person.country.get('name'),\ |
|||
ch.person.country and ch.person.country.get('code'),\ |
|||
ch.person.country and ch.person.country.get('timezone'),\ |
|||
ch.person.image and ch.person.image.get('medium'),\ |
|||
ch.person.url, {TVINFO_TVMAZE: ch.person.id} |
|||
else: |
|||
existing_character.person.append(person) |
|||
else: |
|||
show_obj['cast'][RoleTypes.ActorMain].append( |
|||
Character(p_id=ch.id, name=ch.name, image=ch.image and ch.image.get('original'), |
|||
person=[person], |
|||
plays_self=ch.plays_self, thumb_url=ch.image and ch.image.get('medium') |
|||
)) |
|||
|
|||
if character_person_ids: |
|||
for c, p_ids in iteritems(character_person_ids): |
|||
if p_ids: |
|||
char = next((mc for mc in show_obj['cast'][RoleTypes.ActorMain] if mc.id == c), |
|||
None) # type: Optional[Character] |
|||
if char: |
|||
char.person = [p for p in char.person if p.id not in p_ids] |
|||
|
|||
if show_data.cast: |
|||
show_obj['actors'] = [ |
|||
{'character': {'id': ch.id, |
|||
'name': ch.name, |
|||
'url': 'https://www.tvmaze.com/character/view?id=%s' % ch.id, |
|||
'image': ch.image and ch.image.get('original'), |
|||
}, |
|||
'person': {'id': ch.person and ch.person.id, |
|||
'name': ch.person and ch.person.name, |
|||
'url': ch.person and 'https://www.tvmaze.com/person/view?id=%s' % ch.person.id, |
|||
'image': ch.person and ch.person.image and ch.person.image.get('original'), |
|||
'birthday': None, # not sure about format |
|||
'deathday': None, # not sure about format |
|||
'gender': ch.person and ch.person.gender and ch.person.gender, |
|||
'country': ch.person and ch.person.country and ch.person.country.get('name'), |
|||
}, |
|||
} for ch in show_data.cast.characters] |
|||
|
|||
if show_data.crew: |
|||
for cw in show_data.crew: |
|||
rt = crew_type_names.get(cw.type.lower(), RoleTypes.CrewOther) |
|||
show_obj['crew'][rt].append( |
|||
Crew(p_id=cw.person.id, name=cw.person.name, |
|||
image=cw.person.image and cw.person.image.get('original'), |
|||
gender=cw.person.gender, birthdate=cw.person.birthday, deathdate=cw.person.death_day, |
|||
country=cw.person.country and cw.person.country.get('name'), |
|||
country_code=cw.person.country and cw.person.country.get('code'), |
|||
country_timezone=cw.person.country and cw.person.country.get('timezone'), |
|||
crew_type_name=cw.type, |
|||
) |
|||
) |
|||
|
|||
if show_data.externals: |
|||
show_obj['ids'] = TVInfoIDs(tvdb=show_data.externals.get('thetvdb'), |
|||
rage=show_data.externals.get('tvrage'), |
|||
imdb=show_data.externals.get('imdb') and |
|||
try_int(show_data.externals.get('imdb').replace('tt', ''), None)) |
|||
|
|||
if show_data.network: |
|||
self._set_network(show_obj, show_data.network, False) |
|||
elif show_data.web_channel: |
|||
self._set_network(show_obj, show_data.web_channel, True) |
|||
|
|||
if get_ep_info and not getattr(self.shows.get(sid), 'ep_loaded', False): |
|||
log.debug('Getting all episodes of %s' % sid) |
|||
if None is show_data: |
|||
try: |
|||
self.show_not_found = False |
|||
show_data = tvm_obj.get_show(maze_id=sid, embed='cast%s' % ('', ',episodes')[get_ep_info]) |
|||
except tvmaze.ShowNotFound: |
|||
self.show_not_found = True |
|||
return False |
|||
except (BaseException, Exception) as e: |
|||
log.debug('Error getting data for tvmaze show id: %s' % sid) |
|||
return False |
|||
|
|||
if show_data.episodes: |
|||
specials = [] |
|||
for cur_ep in show_data.episodes: |
|||
if cur_ep.is_special(): |
|||
specials.append(cur_ep) |
|||
else: |
|||
self._set_episode(sid, cur_ep) |
|||
|
|||
if specials: |
|||
specials.sort(key=lambda ep: ep.airstamp or 'Last') |
|||
for ep_n, cur_sp in enumerate(specials, start=1): |
|||
cur_sp.season_number, cur_sp.episode_number = 0, ep_n |
|||
self._set_episode(sid, cur_sp) |
|||
|
|||
if show_data.seasons: |
|||
for cur_s_k, cur_s_v in iteritems(show_data.seasons): |
|||
season_obj = None |
|||
if cur_s_v.season_number not in self.shows[sid]: |
|||
if all(_e.is_special() for _e in cur_s_v.episodes or []): |
|||
season_obj = self.shows[sid][0].__dict__ |
|||
else: |
|||
log.error('error episodes have no numbers') |
|||
season_obj = season_obj or self.shows[sid][cur_s_v.season_number].__dict__ |
|||
for k, v in iteritems(season_map): |
|||
season_obj[k] = getattr(cur_s_v, v, None) or empty_se.get(v) |
|||
if cur_s_v.network: |
|||
self._set_network(season_obj, cur_s_v.network, False) |
|||
elif cur_s_v.web_channel: |
|||
self._set_network(season_obj, cur_s_v.web_channel, True) |
|||
if cur_s_v.image: |
|||
season_obj['poster'] = cur_s_v.image.get('original') |
|||
self.shows[sid].season_images_loaded = True |
|||
|
|||
self.shows[sid].ep_loaded = True |
|||
|
|||
return True |
|||
|
|||
def get_updated_shows(self): |
|||
# type: (...) -> Dict[integer_types, integer_types] |
|||
return {sid: v.seconds_since_epoch for sid, v in iteritems(tvmaze.show_updates().updates)} |
|||
|
|||
@staticmethod |
|||
def _convert_person(person_obj): |
|||
# type: (tvmaze.Person) -> Person |
|||
ch = [] |
|||
for c in person_obj.castcredits or []: |
|||
show = TVInfoShow() |
|||
show.seriesname = c.show.name |
|||
show.id = c.show.id |
|||
show.firstaired = c.show.premiered |
|||
show.ids = TVInfoIDs(ids={TVINFO_TVMAZE: show.id}) |
|||
show.overview = c.show.summary |
|||
show.status = c.show.status |
|||
net = c.show.network or c.show.web_channel |
|||
show.network = net.name |
|||
show.network_id = net.maze_id |
|||
show.network_country = net.country |
|||
show.network_timezone = net.timezone |
|||
show.network_country_code = net.code |
|||
show.network_is_stream = None is not c.show.web_channel |
|||
ch.append(Character(name=c.character.name, show=show)) |
|||
try: |
|||
birthdate = person_obj.birthday and tz_p.parse(person_obj.birthday).date() |
|||
except (BaseException, Exception): |
|||
birthdate = None |
|||
try: |
|||
deathdate = person_obj.death_day and tz_p.parse(person_obj.death_day).date() |
|||
except (BaseException, Exception): |
|||
deathdate = None |
|||
return Person(p_id=person_obj.id, name=person_obj.name, |
|||
image=person_obj.image and person_obj.image.get('original'), |
|||
gender=PersonGenders.named.get(person_obj.gender and person_obj.gender.lower(), |
|||
PersonGenders.unknown), |
|||
birthdate=birthdate, deathdate=deathdate, |
|||
country=person_obj.country and person_obj.country.get('name'), |
|||
country_code=person_obj.country and person_obj.country.get('code'), |
|||
country_timezone=person_obj.country and person_obj.country.get('timezone'), |
|||
thumb_url=person_obj.image and person_obj.image.get('medium'), |
|||
url=person_obj.url, ids={TVINFO_TVMAZE: person_obj.id}, characters=ch |
|||
) |
|||
|
|||
def _search_person(self, name=None, ids=None): |
|||
# type: (AnyStr, Dict[integer_types, integer_types]) -> List[Person] |
|||
urls, result, ids = [], [], ids or {} |
|||
for tv_src in self.supported_person_id_searches: |
|||
if tv_src in ids: |
|||
if TVINFO_TVMAZE == tv_src: |
|||
try: |
|||
r = self.get_person(ids[tv_src]) |
|||
except ConnectionSkipException as e: |
|||
raise e |
|||
except (BaseException, Exception): |
|||
r = None |
|||
if r: |
|||
result.append(r) |
|||
if name: |
|||
try: |
|||
r = tvmaze.people_search(name) |
|||
except ConnectionSkipException as e: |
|||
raise e |
|||
except (BaseException, Exception): |
|||
r = None |
|||
if r: |
|||
for p in r: |
|||
if not any(1 for ep in result if p.id == ep.id): |
|||
result.append(self._convert_person(p)) |
|||
return result |
|||
|
|||
def get_person(self, p_id, get_show_credits=False, get_images=False, **kwargs): |
|||
# type: (integer_types, bool, bool, Any) -> Optional[Person] |
|||
if not p_id: |
|||
return |
|||
kw = {} |
|||
to_embed = [] |
|||
if get_show_credits: |
|||
to_embed.append('castcredits') |
|||
if to_embed: |
|||
kw['embed'] = ','.join(to_embed) |
|||
try: |
|||
p = tvmaze.person_main_info(p_id, **kw) |
|||
except ConnectionSkipException as e: |
|||
raise e |
|||
except (BaseException, Exception): |
|||
p = None |
|||
if p: |
|||
return self._convert_person(p) |
@ -0,0 +1,62 @@ |
|||
# encoding:utf-8 |
|||
|
|||
"""Custom exceptions used or raised by tvmaze_api |
|||
""" |
|||
|
|||
__author__ = 'Prinz23' |
|||
__version__ = '1.0' |
|||
|
|||
__all__ = ['TvMazeException', 'TvMazeError', 'TvMazeUserabort', 'TvMazeShownotfound', |
|||
'TvMazeSeasonnotfound', 'TvMazeEpisodenotfound', 'TvMazeAttributenotfound', 'TvMazeTokenexpired'] |
|||
|
|||
from lib.tvinfo_base.exceptions import * |
|||
|
|||
|
|||
class TvMazeException(BaseTVinfoException): |
|||
"""Any exception generated by tvdb_api |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TvMazeError(BaseTVinfoError, TvMazeException): |
|||
"""An error with thetvdb.com (Cannot connect, for example) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TvMazeUserabort(BaseTVinfoUserabort, TvMazeError): |
|||
"""User aborted the interactive selection (via |
|||
the q command, ^c etc) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TvMazeShownotfound(BaseTVinfoShownotfound, TvMazeError): |
|||
"""Show cannot be found on thetvdb.com (non-existant show) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TvMazeSeasonnotfound(BaseTVinfoSeasonnotfound, TvMazeError): |
|||
"""Season cannot be found on thetvdb.com |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TvMazeEpisodenotfound(BaseTVinfoEpisodenotfound, TvMazeError): |
|||
"""Episode cannot be found on thetvdb.com |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TvMazeAttributenotfound(BaseTVinfoAttributenotfound, TvMazeError): |
|||
"""Raised if an episode does not have the requested |
|||
attribute (such as a episode name) |
|||
""" |
|||
pass |
|||
|
|||
|
|||
class TvMazeTokenexpired(BaseTVinfoAuthenticationerror, TvMazeError): |
|||
"""token expired or missing thetvdb.com |
|||
""" |
|||
pass |
@ -0,0 +1,208 @@ |
|||
# Author: Nic Wolfe <nic@wolfeden.ca> |
|||
# URL: http://code.google.com/p/sickbeard/ |
|||
# |
|||
# This file is part of SickGear. |
|||
# |
|||
# SickGear is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# SickGear is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with SickGear. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
from __future__ import with_statement |
|||
|
|||
import traceback |
|||
|
|||
# noinspection PyPep8Naming |
|||
from exceptions_helper import ex |
|||
|
|||
from . import db, generic_queue, logger, helpers, ui |
|||
|
|||
# noinspection PyUnreachableCode |
|||
if False: |
|||
from six import integer_types |
|||
from typing import AnyStr, Dict, List, Optional |
|||
from .tv import TVShow |
|||
from lib.tvinfo_base import CastList |
|||
|
|||
|
|||
class PeopleQueue(generic_queue.GenericQueue): |
|||
def __init__(self): |
|||
generic_queue.GenericQueue.__init__(self, cache_db_tables=['people_queue']) |
|||
self.queue_name = 'PEOPLEQUEUE' # type: AnyStr |
|||
|
|||
def load_queue(self): |
|||
try: |
|||
my_db = db.DBConnection('cache.db') |
|||
queue_sql = my_db.select('SELECT * FROM people_queue') |
|||
for q in queue_sql: |
|||
if PeopleQueueActions.SHOWCAST == q['action_id']: |
|||
try: |
|||
show_obj = helpers.find_show_by_id({q['indexer']: q['indexer_id']}) |
|||
except (BaseException, Exception): |
|||
continue |
|||
if not show_obj: |
|||
continue |
|||
self.add_cast_update(show_obj=show_obj, show_info_cast=None, uid=q['uid'], force=bool(q['forced']), |
|||
scheduled_update=bool(q['scheduled']), add_to_db=False) |
|||
except (BaseException, Exception) as e: |
|||
logger.log('Exception loading queue %s: %s' % (self.__class__.__name__, ex(e)), logger.ERROR) |
|||
|
|||
def _clear_sql(self): |
|||
return [ |
|||
['DELETE FROM people_queue'] |
|||
] |
|||
|
|||
def _get_item_sql(self, item): |
|||
# type: (PeopleQueueItem) -> List[List] |
|||
return [ |
|||
['INSERT OR IGNORE INTO people_queue (indexer, indexer_id, action_id, forced, scheduled, uid)' |
|||
' VALUES (?,?,?,?,?,?)', |
|||
[item.show_obj._tvid, item.show_obj._prodid, item.action_id, int(item.force), int(item.scheduled_update), |
|||
item.uid]] |
|||
] |
|||
|
|||
def _delete_item_from_db_sql(self, item): |
|||
# type: (PeopleQueueItem) -> List[List] |
|||
return [ |
|||
['DELETE FROM people_queue WHERE uid = ?', [item.uid]] |
|||
] |
|||
|
|||
def queue_data(self): |
|||
# type: (...) -> Dict[AnyStr, List[AnyStr, Dict]] |
|||
data = {'main_cast': []} |
|||
with self.lock: |
|||
for cur_item in [self.currentItem] + self.queue: # type: PeopleQueueItem |
|||
if not cur_item: |
|||
continue |
|||
result_item = {'name': cur_item.show_obj.name, 'tvid_prodid': cur_item.show_obj.tvid_prodid, |
|||
'uid': cur_item.uid, 'forced': cur_item.force} |
|||
if isinstance(cur_item, CastQueueItem): |
|||
data['main_cast'].append(result_item) |
|||
return data |
|||
|
|||
def show_in_queue(self, show_obj): |
|||
# type: (TVShow) -> bool |
|||
with self.lock: |
|||
return any(1 for q in ((self.currentItem and [self.currentItem]) or []) + self.queue |
|||
if show_obj == q.show_obj) |
|||
|
|||
def abort_cast_update(self, show_obj): |
|||
# type: (TVShow) -> None |
|||
if show_obj: |
|||
with self.lock: |
|||
to_remove = [] |
|||
for c in ((self.currentItem and [self.currentItem]) or []) + self.queue: |
|||
if show_obj == c.show_obj: |
|||
try: |
|||
to_remove.append(c.uid) |
|||
except (BaseException, Exception): |
|||
pass |
|||
try: |
|||
c.stop.set() |
|||
except (BaseException, Exception): |
|||
pass |
|||
|
|||
if to_remove: |
|||
try: |
|||
self.remove_from_queue(to_remove) |
|||
except (BaseException, Exception): |
|||
pass |
|||
|
|||
def add_cast_update(self, show_obj, show_info_cast, uid=None, add_to_db=True, force=False, scheduled_update=False, |
|||
switch=False): |
|||
# type: (TVShow, Optional[CastList], AnyStr, bool, bool, bool, bool) -> CastQueueItem |
|||
""" |
|||
|
|||
:param show_obj: TV Show object |
|||
:param show_info_cast: TV Info object |
|||
:param uid: unique id |
|||
:param add_to_db: add to queue db table |
|||
:param force: |
|||
:param scheduled_update: suppresses ui notifications |
|||
:param switch: part of id switch |
|||
""" |
|||
with self.lock: |
|||
if not self.show_in_queue(show_obj): |
|||
cast_item = CastQueueItem(show_obj=show_obj, show_info_cast=show_info_cast, uid=uid, force=force, |
|||
scheduled_update=scheduled_update, switch=switch) |
|||
self.add_item(cast_item, add_to_db=add_to_db) |
|||
return cast_item |
|||
|
|||
|
|||
class PeopleQueueActions(object): |
|||
SHOWCAST = 1 |
|||
|
|||
names = { |
|||
SHOWCAST: 'Show Cast', |
|||
} |
|||
|
|||
|
|||
class PeopleQueueItem(generic_queue.QueueItem): |
|||
def __init__(self, action_id, show_obj, uid=None, force=False, **kwargs): |
|||
# type: (integer_types, TVShow, AnyStr, bool, Dict) -> PeopleQueueItem |
|||
""" |
|||
|
|||
:param action_id: |
|||
:param show_obj: show object |
|||
""" |
|||
generic_queue.QueueItem.__init__(self, PeopleQueueActions.names[action_id], action_id, uid=uid) |
|||
self.show_obj = show_obj # type: TVShow |
|||
self.force = force # type: bool |
|||
|
|||
|
|||
class CastQueueItem(PeopleQueueItem): |
|||
def __init__(self, show_obj, show_info_cast=None, uid=None, force=False, scheduled_update=False, switch=False, |
|||
**kwargs): |
|||
# type: (TVShow, CastList, AnyStr, bool, bool, bool, Dict) -> CastQueueItem |
|||
""" |
|||
|
|||
:param show_obj: show obj |
|||
:param show_info_cast: show info cast list |
|||
:param scheduled_update: suppresses ui notifications |
|||
:param switch: part of id switch |
|||
""" |
|||
PeopleQueueItem.__init__(self, PeopleQueueActions.SHOWCAST, show_obj, uid=uid, force=force, **kwargs) |
|||
self.show_info_cast = show_info_cast # type: Optional[CastList] |
|||
self.scheduled_update = scheduled_update # type: bool |
|||
self.switch = switch # type: bool |
|||
|
|||
def run(self): |
|||
|
|||
PeopleQueueItem.run(self) |
|||
|
|||
if self.show_obj: |
|||
logger.log('Starting to update show cast for %s' % self.show_obj.name) |
|||
old_cast = set((c.name, c.image_url or '', c.thumb_url or '', |
|||
hash(*(p.name for p in c.person or [] if p.name))) |
|||
for c in self.show_obj.cast_list or [] if c.name) |
|||
if not self.scheduled_update and not self.switch: |
|||
ui.notifications.message('Starting to update show cast for %s' % self.show_obj.name) |
|||
try: |
|||
self.show_obj.load_cast_from_tvinfo(self.show_info_cast, force=self.force, stop_event=self.stop) |
|||
except (BaseException, Exception) as e: |
|||
logger.error('Exception in cast update queue: %s' % ex(e)) |
|||
logger.debug('Traceback: %s' % traceback.format_exc()) |
|||
if old_cast != set((c.name, c.image_url or '', c.thumb_url or '', |
|||
hash(*(p.name for p in c.person or [] if p.name))) |
|||
for c in self.show_obj.cast_list or [] if c.name): |
|||
logger.debug('Update show nfo with new cast data') |
|||
self.show_obj.write_show_nfo(force=True) |
|||
logger.log('Finished cast update for show %s' % self.show_obj.name) |
|||
if not self.scheduled_update and not self.switch: |
|||
ui.notifications.message('Finished to update show cast for %s' % self.show_obj.name) |
|||
|
|||
self.finish() |
|||
|
|||
def __str__(self): |
|||
return '<Cast Queue Item (%s)%s>' % (self.show_obj.name, ('', ' - forced')[self.force]) |
|||
|
|||
def __repr__(self): |
|||
return self.__str__() |
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
Loading…
Reference in new issue