add generic subtitle_id to Subtitle class; skip whacking parts directly after sub storage for now; remove necessity of trigger argument for skipping duplicate views; add generic home button;

This commit is contained in:
panni
2016-08-07 05:07:05 +02:00
parent abeb2c96b1
commit b13cbeed61
7 changed files with 182 additions and 54 deletions
+63 -42
View File
@@ -6,11 +6,14 @@ import operator
import logger
import os
import traceback
import subliminal
import subliminal_patch
from subliminal_patch.patch_api import list_all_subtitles
from subliminal.api import download_subtitles
from babelfish import Language
from menu_helpers import add_ignore_options, dig_tree, set_refresh_menu_state, \
should_display_ignore, enable_channel_wrapper, default_thumb, debounce
should_display_ignore, enable_channel_wrapper, default_thumb, debounce, SZObjectContainer
from subliminal_patch.patch_subtitle import compute_score
from subzero.constants import TITLE, ART, ICON, PREFIX, PLUGIN_IDENTIFIER, DEPENDENCY_MODULE_NAMES
from support.background import scheduler
@@ -44,8 +47,8 @@ def fatality(randomize=None, force_title=None, header=None, message=None, only_r
"""
subzero main menu
"""
title = force_title if force_title is not None else config.full_version
oc = ObjectContainer(title1=title, title2=None, header=unicode(header) if header else header, message=message, no_history=no_history,
title = config.full_version#force_title if force_title is not None else config.full_version
oc = ObjectContainer(title1=title, title2=title, header=unicode(header) if header else title, message=message, no_history=no_history,
replace_parent=replace_parent, no_cache=True)
if not config.permissions_ok and config.missing_permissions:
@@ -74,7 +77,7 @@ def fatality(randomize=None, force_title=None, header=None, message=None, only_r
summary="Shows the current on deck items and allows you to individually (force-) refresh their metadata/subtitles."
))
oc.add(DirectoryObject(
key=Callback(RecentlyAddedMenu),
key=Callback(RecentlyAddedMenu, randomize=timestamp()),
title="Items with missing subtitles",
summary="Shows the items honoring the configured 'Item age to be considered recent'-setting (%s)"
" and allowing you to individually (force-) refresh their metadata/subtitles. " % Prefs["scheduler.item_is_recent_age"]
@@ -138,7 +141,8 @@ def OnDeckMenu(message=None):
@route(PREFIX + '/recent')
def RecentlyAddedMenu(message=None):
@debounce
def RecentlyAddedMenu(message=None, randomize=None):
"""
displays the recently added items with missing subtitles
:param message:
@@ -148,7 +152,7 @@ def RecentlyAddedMenu(message=None):
def recentItemsMenu(title, base_title=None):
oc = ObjectContainer(title2=title, no_cache=True, no_history=True)
oc = SZObjectContainer(title2=title, no_cache=True, no_history=True)
recent_items = get_recent_items()
if recent_items:
missing_items = items_get_all_missing_subs(recent_items)
@@ -174,7 +178,7 @@ def mergedItemsMenu(title, itemGetter, itemGetterKwArgs=None, base_title=None, *
:param kwargs:
:return:
"""
oc = ObjectContainer(title2=title, no_cache=True, no_history=True)
oc = SZObjectContainer(title2=title, no_cache=True, no_history=True)
items = itemGetter(*args, **kwargs)
for kind, title, item_id, deeper, item in items:
@@ -212,7 +216,7 @@ def IgnoreMenu(kind, rating_key, title=None, sure=False, todo="not_set"):
"""
is_ignored = rating_key in ignore_list[kind]
if not sure:
oc = ObjectContainer(no_history=True, replace_parent=True, title1="%s %s %s %s the ignore list" % (
oc = SZObjectContainer(no_history=True, replace_parent=True, title1="%s %s %s %s the ignore list" % (
"Add" if not is_ignored else "Remove", ignore_list.verbose(kind), title, "to" if not is_ignored else "from"), title2="Are you sure?")
oc.add(DirectoryObject(
key=Callback(IgnoreMenu, kind=kind, rating_key=rating_key, title=title, sure=True, todo="add" if not is_ignored else "remove"),
@@ -257,7 +261,7 @@ def SectionsMenu():
"""
items = get_all_items("sections")
return dig_tree(ObjectContainer(title2="Sections", no_cache=True, no_history=True), items, None,
return dig_tree(SZObjectContainer(title2="Sections", no_cache=True, no_history=True), items, None,
menu_determination_callback=determine_section_display, pass_kwargs={"base_title": "Sections"},
fill_args={"title": "section_title"})
@@ -280,7 +284,7 @@ def SectionMenu(rating_key, title=None, base_title=None, section_title=None, ign
section_title = title
title = base_title + " > " + title
oc = ObjectContainer(title2=title, no_cache=True, no_history=True)
oc = SZObjectContainer(title2=title, no_cache=True, no_history=True)
if ignore_options:
add_ignore_options(oc, "sections", title=section_title, rating_key=rating_key, callback_menu=IgnoreMenu)
@@ -304,7 +308,7 @@ def SectionFirstLetterMenu(rating_key, title=None, base_title=None, section_titl
kind, deeper = get_items_info(items)
title = unicode(title)
oc = ObjectContainer(title2=section_title, no_cache=True, no_history=True)
oc = SZObjectContainer(title2=section_title, no_cache=True, no_history=True)
title = base_title + " > " + title
add_ignore_options(oc, "sections", title=section_title, rating_key=rating_key, callback_menu=IgnoreMenu)
@@ -329,7 +333,7 @@ def FirstLetterMetadataMenu(rating_key, key, title=None, base_title=None, displa
:return:
"""
title = base_title + " > " + unicode(title)
oc = ObjectContainer(title2=title, no_cache=True, no_history=True)
oc = SZObjectContainer(title2=title, no_cache=True, no_history=True)
items = get_all_items(key="first_character", value=[rating_key, key], base="library/sections", flat=False)
kind, deeper = get_items_info(items)
@@ -354,7 +358,7 @@ def MetadataMenu(rating_key, title=None, base_title=None, display_items=False, p
title = unicode(title)
item_title = title
title = base_title + " > " + title
oc = ObjectContainer(title2=title, no_cache=True, no_history=True)
oc = SZObjectContainer(title2=title, no_cache=True, no_history=True)
current_kind = get_item_kind_from_rating_key(rating_key)
@@ -394,7 +398,7 @@ def MetadataMenu(rating_key, title=None, base_title=None, display_items=False, p
@route(PREFIX + '/ignore_list')
def IgnoreListMenu():
oc = ObjectContainer(title2="Ignore list", replace_parent=True)
oc = SZObjectContainer(title2="Ignore list", replace_parent=True)
for key in ignore_list.key_order:
values = ignore_list[key]
for value in values:
@@ -403,6 +407,7 @@ def IgnoreListMenu():
@route(PREFIX + '/item/{rating_key}/actions')
@debounce
def ItemDetailsMenu(rating_key, title=None, base_title=None, item_title=None, randomize=None):
"""
displays the item details menu of an item that doesn't contain any deeper tree, such as a movie or an episode
@@ -418,7 +423,7 @@ def ItemDetailsMenu(rating_key, title=None, base_title=None, item_title=None, ra
timeout = 30
oc = ObjectContainer(title2=title, replace_parent=True)
oc = SZObjectContainer(title2=title, replace_parent=True)
oc.add(DirectoryObject(
key=Callback(RefreshItem, rating_key=rating_key, item_title=item_title, randomize=timestamp(),
timeout=timeout*1000),
@@ -457,6 +462,7 @@ def ItemDetailsMenu(rating_key, title=None, base_title=None, item_title=None, ra
# try getting current subtitle information for that language
current_subtitle_key = sub_data_for_lang.get("current", (None, None))
current_sub_provider_name, current_sub_id = current_subtitle_key
current_sub_link = None
legacy_storage = False
@@ -474,6 +480,7 @@ def ItemDetailsMenu(rating_key, title=None, base_title=None, item_title=None, ra
summary = u"No current subtitle in storage"
if current_sub_provider_name:
current_subtitle = sub_part_data[lang_short][current_subtitle_key]
current_sub_link = current_subtitle["link"]
summary = u"Current subtitle%s: %s (added: %s), Language: %s, Score: %i, Storage: %s, From: %s" % \
(u" (legacy/inaccurate)" if legacy_storage else "", current_sub_provider_name,
@@ -482,8 +489,8 @@ def ItemDetailsMenu(rating_key, title=None, base_title=None, item_title=None, ra
oc.add(DirectoryObject(
key=Callback(TriggerListAvailableSubsForItem, rating_key=rating_key, part_id=part.id, title=title,
item_title=item_title, language=lang_short,
item_type=plex_item.type, filename=filename),
item_title=item_title, language=lang_short, current_link=current_sub_link,
item_type=plex_item.type, filename=filename, randomize=timestamp()),
title=u"Available subtitles for: %s, %s" % (lang_short, filename),
summary=summary
))
@@ -499,13 +506,16 @@ MANUAL_SUB_SEARCH = {}
@route(PREFIX + '/item/search/{rating_key}/{part_id}')
@debounce
def TriggerListAvailableSubsForItem(rating_key=None, part_id=None, title=None, item_title=None, filename=None,
item_type="episode", language=None, force=False, trigger=True):
item_type="episode", language=None, force=False, current_link=None,
randomize=None):
assert rating_key, part_id
if not trigger:
return
#config.init_subliminal_patches()
plex_item = list(Plex["library"].metadata(rating_key))[0]
#fixme: woot
subliminal.video.Episode.scores["addic7ed_boost"] = int(Prefs['provider.addic7ed.boost_by'])
# find current part
current_part = None
for part in plex_item.media.parts:
@@ -564,40 +574,52 @@ def TriggerListAvailableSubsForItem(rating_key=None, part_id=None, title=None, i
subtitles.append(subtitle)
print subtitles
oc = SZObjectContainer(title2=title, replace_parent=True)
oc.add(DirectoryObject(
key=Callback(ItemDetailsMenu, rating_key=rating_key, item_title=item_title, randomize=timestamp()),
title=u"Back to %s" % title,
summary="",
thumb=default_thumb
))
print current_link
for subtitle in subtitles:
oc.add(DirectoryObject(
key=Callback(RefreshItem, rating_key=rating_key, item_title=item_title, randomize=timestamp()),
title=u"%s: %s, score: %s; %s" % ("Available" if current_link != subtitle.page_link else "Current",
subtitle.provider_name, subtitle.score, subtitle.subtitle_id),
summary="Refreshes the item, possibly picking up new subtitles on disk",
thumb=default_thumb
))
return ItemDetailsMenu(rating_key, randomize=timestamp(), title=title, item_title=item_title)
return oc
@route(PREFIX + '/item/{rating_key}')
@debounce
def RefreshItem(rating_key=None, item_title=None, force=False, refresh_kind=None,
previous_rating_key=None, timeout=8000, randomize=None, trigger=True):
previous_rating_key=None, timeout=8000, randomize=None):
assert rating_key
header = " "
if trigger:
set_refresh_menu_state(u"Triggering %sRefresh for %s" % ("Force-" if force else "", item_title))
Log.Info("Triggering %srefresh of item %s, \"%s\" (timeout: %s)", "" if not force else "force-", rating_key,
item_title, timeout)
Thread.Create(refresh_item, rating_key=rating_key, force=force, refresh_kind=refresh_kind,
parent_rating_key=previous_rating_key, timeout=int(timeout))
header = u"%s of item %s triggered" % ("Refresh" if not force else "Forced-refresh", rating_key)
set_refresh_menu_state(u"Triggering %sRefresh for %s" % ("Force-" if force else "", item_title))
Log.Info("Triggering %srefresh of item %s, \"%s\" (timeout: %s)", "" if not force else "force-", rating_key,
item_title, timeout)
Thread.Create(refresh_item, rating_key=rating_key, force=force, refresh_kind=refresh_kind,
parent_rating_key=previous_rating_key, timeout=int(timeout))
header = u"%s of item %s triggered" % ("Refresh" if not force else "Forced-refresh", rating_key)
return fatality(randomize=timestamp(), header=header, replace_parent=True)
@route(PREFIX + '/missing/refresh')
@debounce
def RefreshMissing(randomize=None, trigger=True):
header = " "
if trigger:
Thread.CreateTimer(1.0, lambda: scheduler.run_task("searchAllRecentlyAddedMissing"))
header = "Refresh of recently added items with missing subtitles triggered"
def RefreshMissing(randomize=None):
Thread.CreateTimer(1.0, lambda: scheduler.run_task("searchAllRecentlyAddedMissing"))
header = "Refresh of recently added items with missing subtitles triggered"
return fatality(header=header, replace_parent=True)
@route(PREFIX + '/advanced')
def AdvancedMenu(randomize=None, header=None, message=None):
oc = ObjectContainer(header=header or "Internal stuff, pay attention!", message=message, no_cache=True, no_history=True,
replace_parent=True, title2="Advanced")
oc = SZObjectContainer(header=header or "Internal stuff, pay attention!", message=message, no_cache=True, no_history=True,
replace_parent=False, title2="Advanced")
oc.add(DirectoryObject(
key=Callback(TriggerRestart, randomize=timestamp()),
@@ -677,10 +699,9 @@ def DispatchRestart():
@route(PREFIX + '/advanced/restart/trigger')
@debounce
def TriggerRestart(randomize=None, trigger=True):
if trigger:
set_refresh_menu_state("Restarting the plugin")
DispatchRestart()
def TriggerRestart(randomize=None):
set_refresh_menu_state("Restarting the plugin")
DispatchRestart()
return fatality(header="Restart triggered, please wait about 5 seconds", force_title=" ", only_refresh=True, replace_parent=True,
no_history=True, randomize=timestamp())
@@ -693,7 +714,7 @@ def Restart():
@route(PREFIX + '/storage/reset', sure=bool)
def ResetStorage(key, randomize=None, sure=False):
if not sure:
oc = ObjectContainer(no_history=True, title1="Reset subtitle storage", title2="Are you sure?")
oc = SZObjectContainer(no_history=True, title1="Reset subtitle storage", title2="Are you sure?")
oc.add(DirectoryObject(
key=Callback(ResetStorage, key=key, sure=True, randomize=timestamp()),
title=pad_title("Are you really sure?"),
+14 -1
View File
@@ -131,8 +131,8 @@ def debounce(func):
def wrap(*args, **kwargs):
if "randomize" in kwargs:
if ([func] + list(args), kwargs) in debouncer:
kwargs["trigger"] = False
Log.Debug("not triggering %s twice with %s, %s" % (func, args, kwargs))
return ObjectContainer()
else:
debouncer.add([func] + list(args), kwargs)
return func(*args, **kwargs)
@@ -140,3 +140,16 @@ def debounce(func):
return wrap
class SZObjectContainer(ObjectContainer):
def __init__(self, *args, **kwargs):
super(SZObjectContainer, self).__init__(*args, **kwargs)
from interface.menu import fatality
from support.helpers import pad_title, timestamp
self.add(DirectoryObject(
key=Callback(fatality, force_title=" ", randomize=timestamp()),
title=pad_title("<< Back to home"),
summary="Current state: %s; Last state: %s" % (
(Dict["current_refresh_state"] or "Idle") if "current_refresh_state" in Dict else "Idle",
(Dict["last_refresh_state"] or "None") if "last_refresh_state" in Dict else "None"
)
))
+15 -9
View File
@@ -2,6 +2,7 @@
import datetime
import pprint
import copy
from helpers import get_video_display_title
@@ -32,10 +33,13 @@ def whack_missing_parts(scanned_video_part_map, existing_parts=None):
if video.id not in Dict["subs"]:
continue
for part_id in Dict["subs"][video.id].keys():
parts = Dict["subs"][video.id].keys()
for part_id in parts:
if part_id not in existing_parts:
Log.Info("Whacking part %s in internal storage of video %s (%s, %s)", part_id, video.id,
repr(existing_parts), repr(parts))
del Dict["subs"][video.id][part_id]
Log.Info("Whacking part %s in internal storage of video %s", part_id, video.id)
whacked_parts = True
if whacked_parts:
@@ -49,16 +53,14 @@ def store_subtitle_info(scanned_video_part_map, downloaded_subtitles, storage_ty
if "subs" not in Dict:
Dict["subs"] = {}
storage = Dict["subs"]
existing_parts = []
for video, video_subtitles in downloaded_subtitles.items():
part = scanned_video_part_map[video]
if video.id not in storage:
storage[video.id] = {}
if video.id not in Dict["subs"]:
Dict["subs"][video.id] = {}
video_dict = storage[video.id]
video_dict = copy.deepcopy(Dict["subs"][video.id])
if part.id not in video_dict:
video_dict[part.id] = {}
@@ -87,8 +89,12 @@ def store_subtitle_info(scanned_video_part_map, downloaded_subtitles, storage_ty
date_added=datetime.datetime.now(), title=title)
lang_dict["current"] = sub_key
if existing_parts:
whack_missing_parts(scanned_video_part_map, existing_parts=existing_parts)
Dict["subs"][video.id] = video_dict
#Dict.Save()
#if existing_parts:
# whack_missing_parts(scanned_video_part_map, existing_parts=existing_parts)
Dict.Save()
@@ -19,7 +19,7 @@ setattr(OpenSubtitlesSubtitle, "__bases__", (PatchedSubtitle,))
from .patch_provider_pool import PatchedProviderPool
from .patch_video import patched_search_external_subtitles, scan_video
from .patch_providers import addic7ed, podnapisi, tvsubtitles, opensubtitles
from .patch_api import save_subtitles
from .patch_api import save_subtitles, list_all_subtitles
# patch subliminal's ProviderPool
subliminal.api.ProviderPool = PatchedProviderPool
@@ -27,6 +27,8 @@ subliminal.api.ProviderPool = PatchedProviderPool
# patch subliminal's save_subtitles function
subliminal.api.save_subtitles = save_subtitles
subliminal.api.list_all_subtitles = list_all_subtitles
# patch subliminal's subtitle classes
def subtitleRepr(self):
link = self.page_link
@@ -19,6 +19,7 @@ USE_BOOST = False
class PatchedAddic7edSubtitle(Addic7edSubtitle):
def __init__(self, *args, **kwargs):
super(PatchedAddic7edSubtitle, self).__init__(*args, **kwargs)
self.subtitle_id = kwargs.get("download_link")
def get_matches(self, video, hearing_impaired=False):
matches = super(PatchedAddic7edSubtitle, self).get_matches(video, hearing_impaired=hearing_impaired)
@@ -2,12 +2,30 @@
import logging
import io
import re
try:
from lxml import etree
except ImportError:
try:
import xml.etree.cElementTree as etree
except ImportError:
import xml.etree.ElementTree as etree
from babelfish import Language
from zipfile import ZipFile
from subliminal.providers.podnapisi import PodnapisiProvider, fix_line_ending, ProviderError
from subliminal.providers.podnapisi import PodnapisiProvider, PodnapisiSubtitle, fix_line_ending, ProviderError
logger = logging.getLogger(__name__)
class PatchedPodnapisiSubtitle(PodnapisiSubtitle):
provider_name = 'podnapisi'
def __init__(self, language, hearing_impaired, page_link, pid, releases, title, season=None, episode=None,
year=None):
super(PodnapisiSubtitle, self).__init__(language, hearing_impaired, page_link)
self.subtitle_id = pid
class PatchedPodnapisiProvider(PodnapisiProvider):
def download_subtitle(self, subtitle):
# download as a zip
@@ -21,3 +39,69 @@ class PatchedPodnapisiProvider(PodnapisiProvider):
raise ProviderError('More than one file to unzip')
subtitle.content = fix_line_ending(zf.read(zf.namelist()[0]))
def query(self, language, keyword, season=None, episode=None, year=None):
# set parameters, see http://www.podnapisi.net/forum/viewtopic.php?f=62&t=26164#p212652
params = {'sXML': 1, 'sL': str(language), 'sK': keyword}
is_episode = False
if season and episode:
is_episode = True
params['sTS'] = season
params['sTE'] = episode
if year:
params['sY'] = year
# loop over paginated results
logger.info('Searching subtitles %r', params)
subtitles = []
pids = set()
while True:
# query the server
xml = etree.fromstring(self.session.get(self.server_url + 'search/old', params=params, timeout=10).content)
# exit if no results
if not int(xml.find('pagination/results').text):
logger.debug('No subtitles found')
break
# loop over subtitles
for subtitle_xml in xml.findall('subtitle'):
# read xml elements
language = Language.fromietf(subtitle_xml.find('language').text)
hearing_impaired = 'n' in (subtitle_xml.find('flags').text or '')
page_link = subtitle_xml.find('url').text
pid = subtitle_xml.find('pid').text
releases = []
if subtitle_xml.find('release').text:
for release in subtitle_xml.find('release').text.split():
releases.append(re.sub(r'\.+$', '', release)) # remove trailing dots
title = subtitle_xml.find('title').text
season = int(subtitle_xml.find('tvSeason').text)
episode = int(subtitle_xml.find('tvEpisode').text)
year = int(subtitle_xml.find('year').text)
if is_episode:
subtitle = PatchedPodnapisiSubtitle(language, hearing_impaired, page_link, pid, releases, title,
season=season, episode=episode, year=year)
else:
subtitle = PatchedPodnapisiSubtitle(language, hearing_impaired, page_link, pid, releases, title,
year=year)
# ignore duplicates, see http://www.podnapisi.net/forum/viewtopic.php?f=62&t=26164&start=10#p213321
if pid in pids:
continue
logger.debug('Found subtitle %r', subtitle)
subtitles.append(subtitle)
pids.add(pid)
# stop on last page
if int(xml.find('pagination/current').text) >= int(xml.find('pagination/count').text):
break
# increment current page
params['page'] = int(xml.find('pagination/current').text) + 1
logger.debug('Getting page %d', params['page'])
return subtitles
@@ -68,6 +68,7 @@ def compute_score(matches, video, scores=None):
class PatchedSubtitle(Subtitle):
storage_path = None
subtitle_id = None
def guess_encoding(self):
"""Guess encoding using the language, falling back on chardet.