Files
Sub-Zero.bundle/Contents/Code/__init__.py
T
2015-12-06 15:04:18 +01:00

321 lines
11 KiB
Python
Executable File

# coding=utf-8
import os
import sys
# just some slight modifications to support sum and iter again
from subzero.sandbox import restore_builtins
module = sys.modules['__main__']
restore_builtins(module, {})
globals = getattr(module, "__builtins__")["globals"]
for key, value in getattr(module, "__builtins__").iteritems():
if key != "globals":
globals()[key] = value
import logger
sys.modules["logger"] = logger
import subliminal
import subliminal_patch
import support
import interface
sys.modules["interface"] = interface
from subzero.constants import OS_PLEX_USERAGENT, PERSONAL_MEDIA_IDENTIFIER
from subzero import intent
from interface.menu import *
from support.subtitlehelpers import getSubtitlesFromMetadata
from support.storage import storeSubtitleInfo
from support.config import config
def Start():
HTTP.CacheTime = 0
HTTP.Headers['User-agent'] = OS_PLEX_USERAGENT
# configured cache to be in memory as per https://github.com/Diaoul/subliminal/issues/303
subliminal.region.configure('dogpile.cache.memory')
# init defaults; perhaps not the best idea to use ValidatePrefs here, but we'll see
ValidatePrefs()
Log.Debug(config.full_version)
if not config.plex_api_working:
Log.Error(lib_unaccessible_error)
return
scheduler.run()
def initSubliminalPatches():
# configure custom subtitle destination folders for scanning pre-existing subs
dest_folder = config.subtitleDestinationFolder
subliminal_patch.patch_video.CUSTOM_PATHS = [dest_folder] if dest_folder else []
subliminal_patch.patch_provider_pool.DOWNLOAD_TRIES = int(Prefs['subtitles.try_downloads'])
subliminal_patch.patch_providers.addic7ed.USE_BOOST = bool(Prefs['provider.addic7ed.boost'])
def flattenToParts(media, kind="series"):
"""
iterates through media and returns the associated parts (videos)
:param media:
:param kind:
:return:
"""
parts = []
if kind == "series":
for season in media.seasons:
for episode in media.seasons[season].episodes:
ep = media.seasons[season].episodes[episode]
for item in media.seasons[season].episodes[episode].items:
for part in item.parts:
parts.append({"video": part, "type": "episode", "title": ep.title, "series": media.title, "id": ep.id})
else:
for item in media.items:
for part in item.parts:
parts.append({"video": part, "type": "movie", "title": media.title, "id": media.id})
return parts
IGNORE_FN = ("subzero.ignore", ".subzero.ignore", ".nosz")
def parseMediaToParts(media, kind="series"):
"""
returns a list of parts to be used later on; ignores folders with an existing "subzero.ignore" file
:param media:
:param kind:
:return:
"""
parts = flattenToParts(media, kind=kind)
if not Prefs["subtitles.ignore_fs"]:
return parts
use_parts = []
check_ignore_paths = [".", "../"]
if kind == "series":
check_ignore_paths.append("../../")
for part in parts:
base_folder, fn = os.path.split(part["video"].file)
ignore = False
for rel_path in check_ignore_paths:
fld = os.path.abspath(os.path.join(base_folder, rel_path))
for ifn in IGNORE_FN:
if os.path.isfile(os.path.join(fld, ifn)):
Log.Info(u'Ignoring "%s" because "%s" exists in "%s"', fn, ifn, fld)
ignore = True
break
if ignore:
break
if not ignore:
use_parts.append(part)
return use_parts
def scanParts(parts, kind="series"):
"""
receives a list of parts containing dictionaries returned by flattenToParts
:param parts:
:param kind: series or movies
:return: dictionary of scanned videos of subliminal.video.scan_video
"""
ret = {}
for part in parts:
force_refresh = intent.get("force", part["id"])
hints = {"expected_title": [part["title"]]}
hints.update({"type": "episode", "expected_series": [part["series"]]} if kind == "series" else {"type": "movie"})
scanned_video = scanVideo(part["video"], ignore_all=force_refresh, hints=hints)
if not scanned_video:
continue
scanned_video.id = part["id"]
ret[scanned_video] = part["video"]
return ret
def getItemIDs(media, kind="series"):
ids = []
if kind == "movies":
ids.append(media.id)
else:
for season in media.seasons:
for episode in media.seasons[season].episodes:
ids.append(media.seasons[season].episodes[episode].id)
return ids
def scanVideo(part, ignore_all=False, hints=None):
embedded_subtitles = not ignore_all and Prefs['subtitles.scan.embedded']
external_subtitles = not ignore_all and Prefs['subtitles.scan.external']
if ignore_all:
Log.Debug("Force refresh intended.")
Log.Debug("Scanning video: %s, subtitles=%s, embedded_subtitles=%s" % (part.file, external_subtitles, embedded_subtitles))
try:
return subliminal.video.scan_video(part.file, subtitles=external_subtitles, embedded_subtitles=embedded_subtitles, hints=hints or {})
except ValueError:
Log.Warn("File could not be guessed by subliminal")
def downloadBestSubtitles(video_part_map, min_score=0):
hearing_impaired = Prefs['subtitles.search.hearingImpaired']
languages = config.langList
if not languages:
return
missing_languages = False
for video, part in video_part_map.iteritems():
if not Prefs['subtitles.save.filesystem']:
# scan for existing metadata subtitles
meta_subs = getSubtitlesFromMetadata(part)
for language, subList in meta_subs.iteritems():
if subList:
video.subtitle_languages.add(language)
Log.Debug("Found metadata subtitle %s for %s", language, video)
if not (languages - video.subtitle_languages):
Log.Debug('All languages %r exist for %s', languages, video)
continue
missing_languages = True
break
if missing_languages:
Log.Debug("Download best subtitles using settings: min_score: %s, hearing_impaired: %s" % (min_score, hearing_impaired))
return subliminal.api.download_best_subtitles(video_part_map.keys(), languages, min_score, hearing_impaired, providers=config.providers,
provider_configs=config.providerSettings)
Log.Debug("All languages for all requested videos exist. Doing nothing.")
def saveSubtitles(videos, subtitles):
if Prefs['subtitles.save.filesystem']:
Log.Debug("Using filesystem as subtitle storage")
saveSubtitlesToFile(subtitles)
storage = "filesystem"
else:
Log.Debug("Using metadata as subtitle storage")
saveSubtitlesToMetadata(videos, subtitles)
storage = "metadata"
storeSubtitleInfo(videos, subtitles, storage)
def saveSubtitlesToFile(subtitles):
fld_custom = Prefs["subtitles.save.subFolder.Custom"].strip() if bool(Prefs["subtitles.save.subFolder.Custom"]) else None
for video, video_subtitles in subtitles.items():
if not video_subtitles:
continue
fld = None
if fld_custom or Prefs["subtitles.save.subFolder"] != "current folder":
# specific subFolder requested, create it if it doesn't exist
fld_base = os.path.split(video.name)[0]
if fld_custom:
if fld_custom.startswith("/"):
# absolute folder
fld = fld_custom
else:
fld = os.path.join(fld_base, fld_custom)
else:
fld = os.path.join(fld_base, Prefs["subtitles.save.subFolder"])
if not os.path.exists(fld):
os.makedirs(fld)
subliminal.api.save_subtitles(video, video_subtitles, directory=fld)
def saveSubtitlesToMetadata(videos, subtitles):
for video, video_subtitles in subtitles.items():
mediaPart = videos[video]
for subtitle in video_subtitles:
mediaPart.subtitles[Locale.Language.Match(subtitle.language.alpha2)][subtitle.page_link] = Proxy.Media(subtitle.content, ext="srt")
def updateLocalMedia(metadata, media, media_type="movies"):
# Look for subtitles
if media_type == "movies":
for item in media.items:
for part in item.parts:
support.localmedia.findSubtitles(part)
return
# Look for subtitles for each episode.
for s in media.seasons:
# If we've got a date based season, ignore it for now, otherwise it'll collide with S/E folders/XML and PMS
# prefers date-based (why?)
if int(s) < 1900 or metadata.guid.startswith(PERSONAL_MEDIA_IDENTIFIER):
for e in media.seasons[s].episodes:
for i in media.seasons[s].episodes[e].items:
# Look for subtitles.
for part in i.parts:
support.localmedia.findSubtitles(part)
else:
pass
class SubZeroAgent(object):
agent_type = None
languages = [Locale.Language.English]
primary_provider = False
def __init__(self, *args, **kwargs):
super(SubZeroAgent, self).__init__(*args, **kwargs)
self.agent_type = "movies" if isinstance(self, Agent.Movies) else "series"
self.name = "Sub-Zero Subtitles (%s, %s)" % ("Movies" if self.agent_type == "movies" else "TV", config.getVersion())
def search(self, results, media, lang):
Log.Debug("Sub-Zero %s, %s search" % (config.version, self.agent_type))
results.Append(MetadataSearchResult(id='null', score=100))
def update(self, metadata, media, lang):
Log.Debug("Sub-Zero %s, %s update called" % (config.version, self.agent_type))
if not media:
Log.Error("Called with empty media, something is really wrong with your setup!")
return
set_refresh_menu_state(media, media_type=self.agent_type)
item_ids = []
try:
initSubliminalPatches()
parts = parseMediaToParts(media, kind=self.agent_type)
use_score = Prefs["subtitles.search.minimumMovieScore" if self.agent_type == "movies" else "subtitles.search.minimumTVScore"]
scanned_parts = scanParts(parts, kind=self.agent_type)
subtitles = downloadBestSubtitles(scanned_parts, min_score=int(use_score))
item_ids = getItemIDs(media, kind=self.agent_type)
if subtitles:
saveSubtitles(scanned_parts, subtitles)
updateLocalMedia(metadata, media, media_type=self.agent_type)
finally:
# update the menu state
set_refresh_menu_state(None)
# notify any running tasks about our finished update
for item_id in item_ids:
scheduler.signal("updated_metadata", item_id)
# resolve existing intent for that id
intent.resolve("force", item_id)
class SubZeroSubtitlesAgentMovies(SubZeroAgent, Agent.Movies):
contributes_to = ['com.plexapp.agents.imdb', 'com.plexapp.agents.xbmcnfo', 'com.plexapp.agents.themoviedb', 'com.plexapp.agents.hama']
class SubZeroSubtitlesAgentTvShows(SubZeroAgent, Agent.TV_Shows):
contributes_to = ['com.plexapp.agents.thetvdb', 'com.plexapp.agents.thetvdbdvdorder', 'com.plexapp.agents.xbmcnfotv', 'com.plexapp.agents.hama']