Compare commits

...

65 Commits

Author SHA1 Message Date
panni 3007c0d57f messed up the versioning. 1.3.23.459 release 2016-01-03 03:16:30 +01:00
panni 5a2b30432c Merge branch 'master' into develop 2016-01-03 03:13:07 +01:00
panni cfb66db035 1.3.20.459 release 2016-01-03 03:11:23 +01:00
panni 1eec18b76d Merge branch 'master' into develop 2016-01-03 02:59:06 +01:00
panni 1d2bfe2195 1.3.20.422 release 2016-01-03 02:58:12 +01:00
panni f4a13b2e7a Merge branch 'master' into 1.3-stable 2016-01-03 02:55:43 +01:00
panni b29667b9f6 1.3.20.422 release 2016-01-03 02:55:01 +01:00
pannal dcd21aab1c Merge pull request #108 from pannal/opensubtitles-smarty
Opensubtitles: Implement tag matching
2016-01-02 22:23:47 +01:00
panni bfbfcd2d8b OpenSubtitles: QueryParameters seems optional 2016-01-01 19:44:34 +01:00
panni bb72181359 OpenSubtitles: move tag above imdb_id 2016-01-01 05:46:21 +01:00
panni 2d0b9ab9f1 OpenSubtitles: fix QueryParameters usage 2016-01-01 05:27:28 +01:00
panni 291f462955 OpenSubtitles: os.path.basename on video.name 2015-12-31 06:06:51 +01:00
panni 74d6de9c78 prefs: rename label for physical ignore 2015-12-31 04:57:45 +01:00
panni 9f99390145 readme: add documentation for physical ignore 2015-12-31 04:57:25 +01:00
panni 8cdf12bafd readme: clarify scan: include embedded subtitles 2015-12-31 04:34:36 +01:00
panni 2b5442a2a8 readme: add plex signup link; minor corrections 2015-12-31 04:31:02 +01:00
panni ebb9f42771 readme: more detailed recommended-steps docs 2015-12-31 04:26:08 +01:00
panni c75e2b778f readme: add registration links to OS and addic7ed #105 2015-12-31 04:23:16 +01:00
panni 1f6d198bf5 add opensubtitles configuration details; add recommended section to usage 2015-12-31 04:19:50 +01:00
panni 30bbfc37fc don't treat embedded forced subtitles as found embedded subtitles; fixes #106 2015-12-31 04:07:44 +01:00
panni 5a693ae673 pep8 2015-12-31 03:50:07 +01:00
panni 38325f84ac OpenSubitles: list_subtitles: provide tag parameter to query 2015-12-31 03:48:14 +01:00
panni 0eebd164ec OpenSubitles: store QueryParameters for debug logging 2015-12-31 03:33:33 +01:00
panni f60b730411 OpenSubitles: treat a tag match like a hash match 2015-12-31 03:16:28 +01:00
panni 16db1db748 remove "format" from the hash validation for now 2015-12-31 03:16:01 +01:00
panni 818cf4bc33 don't fail on empty hint (most likely command line debugging) 2015-12-31 03:15:37 +01:00
panni 789b7ba9aa Merge remote-tracking branch 'origin/master' into opensubtitles-smarty 2015-12-31 02:40:58 +01:00
pannal fdf389f62c Merge pull request #107 from infernix/master
Define sub_dir_* only if use_filesystem is true
2015-12-29 17:00:28 +01:00
Gerben Meijer 8ae8433463 Define sub_dir_* only if use_filesystem is true 2015-12-29 14:09:23 +01:00
pannal 71464cd5bf Merge pull request #104 from pannal/deeper_guessit_hinting
fix video referenced before assignment; hint guessit two parent folde…
2015-12-28 16:44:05 +01:00
panni 44ca3b9e34 fix video referenced before assignment; hint guessit two parent folders of an episode and one of a movie 2015-12-14 19:53:08 +01:00
pannal 2dd24f02c6 MediaTree has no len 2015-12-06 15:04:18 +01:00
panni a6e6bc810a error if media not given 2015-12-06 06:28:13 +01:00
panni 9d00a82343 move IGNORE_FN 2015-12-06 06:24:59 +01:00
panni 1246c53c77 Merge remote-tracking branch 'origin/master' 2015-12-06 06:22:45 +01:00
panni 8fc10c873e move flattenToParts 2015-12-06 06:22:12 +01:00
panni b48aac638f Merge remote-tracking branch 'origin/master' into 1.3-stable 2015-12-06 06:20:28 +01:00
panni e427565fcf add filesystem ignore mode; fixes #87 2015-12-06 06:18:35 +01:00
panni 0e028b3ffe flatten the agents even more 2015-12-06 05:33:28 +01:00
panni c81e3a7def generify scanTvMedia and scanMovieMedia to scanParts 2015-12-06 05:23:55 +01:00
pannal 669c9b4fb7 Update README.md 2015-12-05 15:17:36 +01:00
panni 5f015c3d69 1.3.20.422 2015-12-05 04:50:57 +01:00
panni faa46a7e4d update settings descriptions 2015-12-05 04:32:50 +01:00
panni 70d2a225f3 do not retry on generic providererror 2015-12-05 04:22:02 +01:00
panni 1521a77281 catch ProviderError; fixes #60 2015-12-05 04:20:50 +01:00
panni 516551714b tvsubtitles: re-re-fix dashes in series name matching; stupid; fixes #93 2015-12-05 04:13:18 +01:00
panni e794122b7f addic7ed: match show ids with language modifier to non-modifier (US/UK...); fixes #90 2015-12-05 03:50:32 +01:00
panni 67282d1ebd reuse use_filesystem instead of accessing prefs again 2015-12-05 03:18:00 +01:00
panni 2c5975cf26 Merge remote-tracking branch 'origin/master' 2015-12-05 03:17:18 +01:00
panni dc142281f5 really skip filesystem if only metadata is wanted; fixes #94 2015-12-05 03:16:42 +01:00
pannal 5a445fc5bd Merge pull request #96 from Erliz/master
Add hama in to supported agents
2015-12-04 01:32:24 +01:00
pannal 7ff2f97ac3 Merge pull request #92 from pannal/unicode_test
fix unicode problems
2015-12-01 01:21:32 +01:00
pannal d47492188e unicodize title parameter in SectionMenu 2015-11-30 19:46:25 +01:00
panni 263d3e7546 use http by default, not https, for local API queries 2015-11-29 04:05:18 +01:00
panni ce31bf63e9 newline 2015-11-29 03:57:25 +01:00
panni 3c030dd6c3 use UnicodeDammit for path 2015-11-29 03:07:09 +01:00
panni 147c3dfe9d Merge remote-tracking branch 'origin/master' 2015-11-28 01:36:29 +01:00
panni c2e820f851 encoding test 2015-11-28 01:35:17 +01:00
pannal f53f5f1870 CFBundleShortVersionString 1.3.20 2015-11-27 15:00:44 +01:00
panni c20ecaa616 Merge remote-tracking branch 'origin/1.3-stable' into 1.3-stable 2015-11-27 02:03:24 +01:00
panni 1e73b530ed leftover import 2015-11-27 01:43:29 +01:00
panni 5c4a1275fb Merge branch 'master' into opensubtitles-smarty
Conflicts:
	Contents/Libraries/Shared/subliminal_patch/patch_providers/opensubtitles.py
2015-11-27 00:08:13 +01:00
Stanislav Vetlovskiy 50ecf71879 Add hama in to supported agents 2015-11-26 22:29:00 +03:00
pannal eca358e73a Merge pull request #85 from pannal/master
new stable
2015-11-22 03:48:43 +01:00
panni 014f34d813 add tag to possible opensubtitles query 2015-11-20 00:50:36 +01:00
16 changed files with 306 additions and 101 deletions
+18
View File
@@ -1,3 +1,21 @@
1.3.20.422
- tvsubtitles: show matching was partially broken
- addic7ed: better show matching
- core: correctly skip subtitles stored in filesystem if metadata storage was selected (Local Media Assets agent may still pick them up)
- core: fix local API access (switch from HTTPS to HTTP)
- core: fix handling of library names and media paths with non-ascii chars in it
- core: fix bundle version to correctly display current bundle version
- core: skip downloading multi-CD subtitle
- settings: clarify
1.3.20.403
- core: handle & and - ("and" and dash) in names
- core: fixed handling of internal metadata subtitles
- re-upped the minimum tv score to 85 (may be even higher in the future)
- opensubtitles: possibly significantly better movie matching (now also query for movie title, instead of only querying for video hash)
1.3.20.396
- core: fix logging handlers (when saving log_level settings loggers got duplicated)
- core: better movie matching by only hinting the filename and the last subdirectory to guessit (instead of the full path)
+87 -41
View File
@@ -57,36 +57,85 @@ def initSubliminalPatches():
subliminal_patch.patch_providers.addic7ed.USE_BOOST = bool(Prefs['provider.addic7ed.boost'])
def scanTvMedia(media):
videos = {}
for season in media.seasons:
for episode in media.seasons[season].episodes:
ep = media.seasons[season].episodes[episode]
force_refresh = intent.get("force", ep.id)
for item in media.seasons[season].episodes[episode].items:
for part in item.parts:
scanned_video = scanVideo(part, ignore_all=force_refresh,
hints={"type": "episode", "expected_series": [media.title], "expected_title": [ep.title]})
if not scanned_video:
continue
scanned_video.id = media.seasons[season].episodes[episode].id
videos[scanned_video] = part
return videos
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
def scanMovieMedia(media):
videos = {}
force_refresh = intent.get("force", media.id)
for item in media.items:
for part in item.parts:
scanned_video = scanVideo(part, ignore_all=force_refresh, hints={"type": "movie", "expected_title": [media.title]})
if not scanned_video:
continue
IGNORE_FN = ("subzero.ignore", ".subzero.ignore", ".nosz")
scanned_video.id = media.id
videos[scanned_video] = part
return videos
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"):
@@ -231,16 +280,23 @@ class SubZeroAgent(object):
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()
videos, subtitles = getattr(self, "update_%s" % self.agent_type)(metadata, media, lang)
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(videos, subtitles)
saveSubtitles(scanned_parts, subtitles)
updateLocalMedia(metadata, media, media_type=self.agent_type)
@@ -255,20 +311,10 @@ class SubZeroAgent(object):
# resolve existing intent for that id
intent.resolve("force", item_id)
def update_movies(self, metadata, media, lang):
videos = scanMovieMedia(media)
subtitles = downloadBestSubtitles(videos, min_score=int(Prefs["subtitles.search.minimumMovieScore"]))
return videos, subtitles
def update_series(self, metadata, media, lang):
videos = scanTvMedia(media)
subtitles = downloadBestSubtitles(videos, min_score=int(Prefs["subtitles.search.minimumTVScore"]))
return videos, subtitles
class SubZeroSubtitlesAgentMovies(SubZeroAgent, Agent.Movies):
contributes_to = ['com.plexapp.agents.imdb', 'com.plexapp.agents.xbmcnfo', 'com.plexapp.agents.themoviedb']
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']
contributes_to = ['com.plexapp.agents.thetvdb', 'com.plexapp.agents.thetvdbdvdorder', 'com.plexapp.agents.xbmcnfotv', 'com.plexapp.agents.hama']
+1
View File
@@ -195,6 +195,7 @@ def SectionMenu(rating_key, title=None, base_title=None, section_title=None, ign
items = getAllItems(key="all", value=rating_key, base="library/sections")
kind, deeper = get_items_info(items)
title = unicode(title)
section_title = title
title = base_title + " > " + title
+2 -2
View File
@@ -31,7 +31,7 @@ def getSectionSize(key):
:return:
"""
size = None
url = "https://127.0.0.1:32400/library/sections/%s/all" % int(key)
url = "http://127.0.0.1:32400/library/sections/%s/all" % int(key)
use_args = {
"X-Plex-Container-Size": "0",
"X-Plex-Container-Start": "0"
@@ -136,7 +136,7 @@ def getRecentItems():
if section.type == "show":
use_args["type"] = "4"
url = "https://127.0.0.1:32400/library/sections/%s/all" % int(section.key)
url = "http://127.0.0.1:32400/library/sections/%s/all" % int(section.key)
response = query_plex(url, use_args)
matcher = episode_re if section.type == "show" else movie_re
+29 -26
View File
@@ -12,36 +12,39 @@ def findSubtitles(part):
lang_sub_map = {}
part_filename = helpers.unicodize(part.file)
part_basename = os.path.splitext(os.path.basename(part_filename))[0]
paths = [os.path.dirname(part_filename)]
use_filesystem = bool(Prefs["subtitles.save.filesystem"])
paths = [os.path.dirname(part_filename)] if use_filesystem else []
# Check for local subtitles subdirectory
sub_dirs_default = ["sub", "subs", "subtitle", "subtitles"]
sub_dir_base = paths[0]
global_subtitle_folder = None
sub_dir_list = []
if use_filesystem:
# Check for local subtitles subdirectory
sub_dir_base = paths[0]
if Prefs["subtitles.save.subFolder"] != "current folder":
# got selected subfolder
sub_dir_list.append(os.path.join(sub_dir_base, Prefs["subtitles.save.subFolder"]))
sub_dir_list = []
sub_dir_custom = Prefs["subtitles.save.subFolder.Custom"].strip() if bool(Prefs["subtitles.save.subFolder.Custom"]) else None
if sub_dir_custom:
# got custom subfolder
if sub_dir_custom.startswith("/"):
# absolute folder
sub_dir_list.append(sub_dir_custom)
else:
# relative folder
sub_dir_list.append(os.path.join(sub_dir_base, sub_dir_custom))
if Prefs["subtitles.save.subFolder"] != "current folder":
# got selected subfolder
sub_dir_list.append(os.path.join(sub_dir_base, Prefs["subtitles.save.subFolder"]))
for sub_dir in sub_dir_list:
if os.path.isdir(sub_dir):
paths.append(sub_dir)
sub_dir_custom = Prefs["subtitles.save.subFolder.Custom"].strip() if bool(Prefs["subtitles.save.subFolder.Custom"]) else None
if sub_dir_custom:
# got custom subfolder
if sub_dir_custom.startswith("/"):
# absolute folder
sub_dir_list.append(sub_dir_custom)
else:
# relative folder
sub_dir_list.append(os.path.join(sub_dir_base, sub_dir_custom))
# Check for a global subtitle location
global_subtitle_folder = os.path.join(Core.app_support_path, 'Subtitles')
if os.path.exists(global_subtitle_folder):
paths.append(global_subtitle_folder)
for sub_dir in sub_dir_list:
if os.path.isdir(sub_dir):
paths.append(sub_dir)
# Check for a global subtitle location
global_subtitle_folder = os.path.join(Core.app_support_path, 'Subtitles')
if os.path.exists(global_subtitle_folder):
paths.append(global_subtitle_folder)
# We start by building a dictionary of files to their absolute paths. We also need to know
# the number of media files that are actually present, in case the found local media asset
@@ -78,7 +81,7 @@ def findSubtitles(part):
# If the file is located within the global subtitle folder and it's name doesn't match exactly
# then we should simply ignore it.
#
if file_path.count(global_subtitle_folder) and not filename_matches_part:
if global_subtitle_folder and file_path.count(global_subtitle_folder) and not filename_matches_part:
continue
# If we have more than one media file within the folder and located filename doesn't match
@@ -100,7 +103,7 @@ def findSubtitles(part):
lang_sub_map[new_language] = lang_sub_map[new_language] + subtitles
# add known metadata subs to our sub list
if not Prefs['subtitles.save.filesystem']:
if not use_filesystem:
for language, sub_list in subtitlehelpers.getSubtitlesFromMetadata(part).iteritems():
if sub_list:
if language not in lang_sub_map:
+9 -3
View File
@@ -252,7 +252,7 @@
},
{
"id": "provider.addic7ed.boost",
"label": "Addic7ed: boost over hash score if requirements met (prefer over other providers)",
"label": "Addic7ed: prefer over other providers (if requirements met)",
"type": "bool",
"default": "false"
},
@@ -264,13 +264,13 @@
},
{
"id": "subtitles.scan.embedded",
"label": "Scan: include subtitles embedded in the media file (and don't download seperate ones)",
"label": "Scan: include embedded subtitles (in the media file (MKV/MP4), don't download if existing)",
"type": "bool",
"default": "false"
},
{
"id": "subtitles.scan.external",
"label": "Scan: include external subtitles (and don't download new ones)",
"label": "Scan: include external subtitles (metadata/filesystem, don't download if existing)",
"type": "bool",
"default": "true"
},
@@ -377,6 +377,12 @@
"type": "bool",
"default": "true"
},
{
"id": "subtitles.ignore_fs",
"label": "Ignore folders (with \"subzero.ignore/.subzero.ignore/.nosz\" files in them)",
"type": "bool",
"default": "false"
},
{
"id": "scheduler.tasks.searchAllRecentlyAddedMissing",
"label": "Scheduler: Periodically search for recent items with missing subtitles",
+3 -3
View File
@@ -9,11 +9,11 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>1.3.6</string>
<string>1.3.20</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.3.20.403</string>
<string>1.3.23.459</string>
<key>PlexFrameworkVersion</key>
<string>2</string>
<key>PlexPluginClass</key>
@@ -32,7 +32,7 @@
&lt;h1&gt;Sub-Zero for Plex&lt;/h1&gt;&lt;i&gt;Subtitles done right&lt;/i&gt;
Version 1.3.20.403
Version 1.3.23.459
Originally based on @bramwalet's awesome &lt;a href=&quot;https://github.com/bramwalet/Subliminal.bundle&quot;&gt;Subliminal.bundle&lt;/a&gt;
@@ -17,6 +17,7 @@ logger = logging.getLogger(__name__)
series_year_re = re.compile('^(?P<series>[ \w]+)(?: \((?P<year>\d{4})\))?$')
class Addic7edSubtitle(Subtitle):
provider_name = 'addic7ed'
@@ -1,6 +1,7 @@
# coding=utf-8
import os
import logging
from bs4 import UnicodeDammit
from subliminal.api import get_subtitle_path, io
from subzero.lib.io import getViableEncoding
@@ -45,7 +46,8 @@ def save_subtitles(video, subtitles, single=False, directory=None, encoding=None
if directory is not None:
subtitle_path = os.path.join(directory, os.path.split(subtitle_path)[1])
subtitle_path = subtitle_path.encode(getViableEncoding())
# force unicode
subtitle_path = UnicodeDammit(subtitle_path).unicode_markup
# save content as is or in the specified encoding
logger.info('Saving %r to %r', subtitle, subtitle_path)
@@ -61,4 +63,4 @@ def save_subtitles(video, subtitles, single=False, directory=None, encoding=None
if single:
break
return saved_subtitles
return saved_subtitles
@@ -8,6 +8,7 @@ import operator
import time
from babelfish.exceptions import LanguageReverseError
from pkg_resources import EntryPoint, iter_entry_points
from subliminal import ProviderError
from subliminal.api import ProviderPool
from subliminal_patch.patch_subtitle import compute_score
@@ -213,6 +214,9 @@ class PatchedProviderPool(ProviderPool):
self[subtitle.provider_name].download_subtitle(subtitle)
except (requests.Timeout, socket.timeout):
logger.error('Provider %r timed out', subtitle.provider_name)
except ProviderError:
logger.error('Unexpected error in provider %r, Traceback: %s', subtitle.provider_name, traceback.format_exc())
break
except:
logger.exception('Unexpected error in provider %r, Traceback: %s', subtitle.provider_name, traceback.format_exc())
else:
@@ -281,7 +285,7 @@ class PatchedProviderPool(ProviderPool):
continue
# bail out if hearing_impaired was wrong
if not "hearing_impaired" in matches and hearing_impaired in ("force HI", "force non-HI"):
if "hearing_impaired" not in matches and hearing_impaired in ("force HI", "force non-HI"):
logger.debug('Skipping subtitle: %r with score %d because hearing-impaired set to %s', subtitle, score, hearing_impaired)
continue
@@ -10,6 +10,7 @@ from .mixins import PunctuationMixin
logger = logging.getLogger(__name__)
series_year_re = re.compile('^(?P<series>.+?)(?: \((?P<year>\d{4})\))?$')
remove_brackets_re = re.compile("^(.+?)( \([^\d]+\))$")
USE_BOOST = False
@@ -106,6 +107,15 @@ class PatchedAddic7edProvider(PunctuationMixin, Addic7edProvider):
logger.debug('Getting show id')
show_id = show_ids.get(series_clean)
if not show_id:
# show not found, try to match it without modifiers (mostly country codes such as US/UK)
match = remove_brackets_re.match(series_clean)
if match:
matched = match.group(1)
show_id = show_ids.get(matched)
if show_id:
logger.debug("show '%s' matched to '%s': %s", series, matched, show_id)
# search as last resort
if not show_id:
logger.warning('Series not found in show ids, attempting search')
@@ -3,12 +3,32 @@
import logging
import os
from babelfish import Language
from subliminal.exceptions import ConfigurationError
from subliminal.providers.opensubtitles import OpenSubtitlesProvider, checked, get_version, __version__, Episode, Movie
from subliminal.providers.opensubtitles import OpenSubtitlesProvider, checked, get_version, __version__, OpenSubtitlesSubtitle, Episode
logger = logging.getLogger(__name__)
class PatchedOpenSubtitlesSubtitle(OpenSubtitlesSubtitle):
def __init__(self, language, hearing_impaired, page_link, subtitle_id, matched_by, movie_kind, hash, movie_name,
movie_release_name, movie_year, movie_imdb_id, series_season, series_episode, query_parameters):
super(PatchedOpenSubtitlesSubtitle, self).__init__(language, hearing_impaired, page_link, subtitle_id, matched_by, movie_kind, hash,
movie_name,
movie_release_name, movie_year, movie_imdb_id, series_season, series_episode)
self.query_parameters = query_parameters or {}
def get_matches(self, video, hearing_impaired=False):
matches = super(PatchedOpenSubtitlesSubtitle, self).get_matches(video, hearing_impaired=hearing_impaired)
# matched by tag?
if self.matched_by == "tag":
# treat a tag match equally to a hash match
logger.debug("Subtitle matched by tag, treating it as a hash-match. Tag: '%s'", self.query_parameters.get("tag", None))
matches.add("hash")
return matches
class PatchedOpenSubtitlesProvider(OpenSubtitlesProvider):
def __init__(self, username=None, password=None):
if username is not None and password is None or username is None and password is not None:
@@ -32,17 +52,73 @@ class PatchedOpenSubtitlesProvider(OpenSubtitlesProvider):
:param languages:
:return:
patch: query movies even if hash is known
patch: query movies even if hash is known; add tag parameter
"""
query = season = episode = None
season = episode = None
if isinstance(video, Episode):
query = video.series
season = video.season
episode = video.episode
#elif ('opensubtitles' not in video.hashes or not video.size) and not video.imdb_id:
# elif ('opensubtitles' not in video.hashes or not video.size) and not video.imdb_id:
# query = video.name.split(os.sep)[-1]
else:
query = video.title
return self.query(languages, hash=video.hashes.get('opensubtitles'), size=video.size, imdb_id=video.imdb_id,
query=query, season=season, episode=episode)
query=query, season=season, episode=episode, tag=os.path.basename(video.name))
def query(self, languages, hash=None, size=None, imdb_id=None, query=None, season=None, episode=None, tag=None):
# fill the search criteria
criteria = []
if hash and size:
criteria.append({'moviehash': hash, 'moviebytesize': str(size)})
if tag:
criteria.append({'tag': tag})
if imdb_id:
criteria.append({'imdbid': imdb_id})
if query and season and episode:
criteria.append({'query': query, 'season': season, 'episode': episode})
elif query:
criteria.append({'query': query})
if not criteria:
raise ValueError('Not enough information')
# add the language
for criterion in criteria:
criterion['sublanguageid'] = ','.join(sorted(l.opensubtitles for l in languages))
# query the server
logger.info('Searching subtitles %r', criteria)
response = checked(self.server.SearchSubtitles(self.token, criteria))
subtitles = []
# exit if no data
if not response['data']:
logger.info('No subtitles found')
return subtitles
# loop over subtitle items
for subtitle_item in response['data']:
# read the item
language = Language.fromopensubtitles(subtitle_item['SubLanguageID'])
hearing_impaired = bool(int(subtitle_item['SubHearingImpaired']))
page_link = subtitle_item['SubtitlesLink']
subtitle_id = int(subtitle_item['IDSubtitleFile'])
matched_by = subtitle_item['MatchedBy']
movie_kind = subtitle_item['MovieKind']
hash = subtitle_item['MovieHash']
movie_name = subtitle_item['MovieName']
movie_release_name = subtitle_item['MovieReleaseName']
movie_year = int(subtitle_item['MovieYear']) if subtitle_item['MovieYear'] else None
movie_imdb_id = int(subtitle_item['IDMovieImdb'])
series_season = int(subtitle_item['SeriesSeason']) if subtitle_item['SeriesSeason'] else None
series_episode = int(subtitle_item['SeriesEpisode']) if subtitle_item['SeriesEpisode'] else None
query_parameters = subtitle_item.get("QueryParameters")
subtitle = PatchedOpenSubtitlesSubtitle(language, hearing_impaired, page_link, subtitle_id, matched_by, movie_kind,
hash, movie_name, movie_release_name, movie_year, movie_imdb_id,
series_season, series_episode, query_parameters)
logger.debug('Found subtitle %r', subtitle)
subtitles.append(subtitle)
return subtitles
@@ -1,13 +1,18 @@
# coding=utf-8
import re
import logging
from subliminal.providers import ParserBeautifulSoup
from subliminal.cache import SHOW_EXPIRATION_TIME, region
from subliminal.providers.tvsubtitles import TVsubtitlesProvider, link_re
from subliminal.providers.tvsubtitles import TVsubtitlesProvider
from .mixins import PunctuationMixin
logger = logging.getLogger(__name__)
# clean_punctuation actually removes the dash in YYYY-YYYY year range
# fixme: clean_punctuation is stupid
link_re = re.compile('^(?P<series>.+?)(?: \(?\d{4}\)?| \((?:US|UK)\))? \((?P<first_year>\d{4})\d{4}\)$')
class PatchedTVsubtitlesProvider(PunctuationMixin, TVsubtitlesProvider):
@region.cache_on_arguments(expiration_time=SHOW_EXPIRATION_TIME)
@@ -29,8 +29,8 @@ def compute_score(matches, video, scores=None):
is_episode = isinstance(video, Episode)
episode_hash_valid_if = {"series", "season", "episode", "format"}
movie_hash_valid_if = {"title", "format", "video_codec"}
episode_hash_valid_if = {"series", "season", "episode"}
movie_hash_valid_if = {"title", "video_codec"}
# remove equivalent match combinations
if 'hash' in final_matches:
@@ -79,6 +79,8 @@ def scan_video(path, subtitles=True, embedded_subtitles=True, hints=None, dont_u
# patch: suggest video type to guessit beforehand
"""
hints = hints or {}
# check for non-existing path
if not dont_use_actual_file and not os.path.exists(path):
raise ValueError('Path does not exist')
@@ -88,9 +90,11 @@ def scan_video(path, subtitles=True, embedded_subtitles=True, hints=None, dont_u
raise ValueError('%s is not a valid video extension' % os.path.splitext(path)[1])
dirpath, filename = os.path.split(path)
# hint guessit the filename itself and its 2 parent directories if we're an episode (most likely Series name/Season/filename), else only one
guess_from = os.path.join(*os.path.normpath(path).split(os.path.sep)[-3 if hints.get("type") == "episode" else -2:])
hints = hints or {}
logger.info('Scanning video (hints: %s) %r in %r', hints, filename, dirpath)
guess_from = os.path.join(os.path.split(dirpath)[-1], filename)
logger.info('Scanning video (hints: %s) %r', hints, guess_from)
# guess
try:
@@ -114,6 +118,7 @@ def scan_video(path, subtitles=True, embedded_subtitles=True, hints=None, dont_u
video.subtitle_languages |= set(patched_search_external_subtitles(path).values())
except Exception:
logger.error("Something went wrong when running guessit: %s", traceback.format_exc())
return
# video metadata with enzyme
try:
@@ -167,6 +172,9 @@ def scan_video(path, subtitles=True, embedded_subtitles=True, hints=None, dont_u
if embedded_subtitles:
embedded_subtitle_languages = set()
for st in mkv.subtitle_tracks:
if st.forced:
logger.debug("Ignoring forced subtitle track %r", st)
continue
if st.language:
try:
embedded_subtitle_languages.add(Language.fromalpha3b(st.language))
+38 -13
View File
@@ -1,4 +1,4 @@
Sub-Zero for Plex, 1.3.20.403
Sub-Zero for Plex, 1.3.23.459
=================
![logo](https://raw.githubusercontent.com/pannal/Sub-Zero.bundle/master/Contents/Resources/subzero.gif)
@@ -12,7 +12,7 @@ If you like this, buy me a beer: [![Donate](https://www.paypalobjects.com/en_US/
### Installation
* go to ```Library/Application Support/Plex Media Server/Plug-ins/```
* ```rm -r Sub-Zero.bundle```
* ```rm -r Sub-Zero.bundle``` (remove the folder)
* get the release you want from *https://github.com/pannal/Sub-Zero.bundle/releases/*
* unzip the release
* restart your plex media server!!!
@@ -25,14 +25,22 @@ Use the following agent order:
2. Local Media Assets
3. anything else
##### Recommended steps
Create an account and provide your credentials (in the plugin configuration) for:
* [Addic7ed](http://www.addic7ed.com/newaccount.php)
* [Opensubtitles](http://www.opensubtitles.org/en/newuser)
* [Plex](https://plex.tv/users/sign_up)
### Attention on the initial refresh
When you first use this plugin, and do a refresh on all of your media, you are most likely
to be shut out by some or all of the subtitle providers depending on your libraries' size.
When you first use this plugin and run a refresh on all of your media, you may be
blacklisted out of excessive usage by some or all of the subtitle providers depending on your library's size.
This will result in a bunch of errors in the log files as well as missing subtitles.
Just be patient, after a day most of those providers will allow you access again and you can
Just be patient, after a day most of those providers will allow you to access them again and you can
refresh the remaining items. If you use the default settings, this will also skip the items
it has already downloaded all the wanted languages for.
it has already downloaded all the wanted languages for. Also, as subtitles will be missing, the scheduler should pick up
the items with missing subtitles automatically.
### Encountered a bug?
* be sure to post your logs:
@@ -42,15 +50,19 @@ it has already downloaded all the wanted languages for.
## Changelog
1.3.20.403
- core: handle & and - ("and" and dash) in names
- core: fixed handling of internal metadata subtitles
- re-upped the minimum tv score to 85 (may be even higher in the future)
- opensubtitles: possibly significantly better movie matching (now also query for movie title, instead of only querying for video hash)
1.3.23.459
- core: slight code cleanup and fixes
- core: add physical (filesystem) ignore mode (create files named `subzero.ignore`, `.subzero.ignore`, `.nosz` to ignore specific files/seasons/series/libraries)
- core: fix guessit hinting of tv series with rare folder layout (e.g. series_name/a/S01E01.mkv)
- core: remove "format" necessity from (opensubtitles) hash-validation
- OpenSubtitles: dramatically improve matching: add tag (exact filename) matching and treat it just like hash matches
- core: ignore embedded forced subtitles (fixes #106)
- docs: update
- settings: clarify
[older changes](CHANGELOG.md)
Description
------------
@@ -70,11 +82,12 @@ Several options are provided in the preferences of this agent.
* Addic7ed username/password: Provide your addic7ed username here, otherwise the provider won't work. Please make sure your account is activated, before using the agent.
* Plex.tv username/password: Generally recommended to be provided; needed if you use Plex Home to make the API work (the whole channel menu depends on it)
* Opensubtitles username/password: Generally recommended to be provided (not necessarily needed, but avoids errors)
* Subtitle language (1)/(2)/(3): Your preferred languages to download subtitles for.
* Additional Subtitle Languages: Additional languages to download; comma-separated; use [ISO-639-1 codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes))
* Provider: Enable ...: Enable/disable this provider. Affects both movies and series.
* Addic7ed: boost over hash score if requirements met: if an Addic7ed subtitle matches the video's series, season, episode, year, and format (e.g. WEB-DL), boost its score, possibly over OpenSubtitles/TheSubDB direct hash match
* Scan: Include embedded subtitles: When enabled, subliminal finds embedded subtitles that are already present within the media file.
* Scan: Include embedded subtitles: When enabled, subliminal finds embedded subtitles (ignoring forced) that are already present within the media file.
* Scan: Include external subtitles: When enabled, subliminal finds subtitles located near the media file on the filesystem.
* Minimum score for download: When configured, what is the minimum score for subtitles to download them? Lower scored subtitles are not downloaded.
* Download hearing impaired subtitles:
@@ -86,6 +99,7 @@ Several options are provided in the preferences of this agent.
* Subtitle folder: (default: current media file's folder) See Store as metadata or on filesystem
* Custom Subtitle folder: See Store as metadata or on filesystem
* Treat IETF language tags as ISO 639-1: Treats subtitle files with IETF language identifiers, such as pt-BR, as their ISO 639-1 counterpart. Thus "pt-BR" will be shown as "Portuguese" instead of "Unknown"
* Ignore folders (...): If a folder contains one of the files named `subzero.ignore`, `.subzero.ignore`, `.nosz`, don't process them. This applies to sections/libraries, movies, series, seasons, episodes
* Scheduler:
* Periodically search for recent items with missing subtitles: self-explanatory, executes the task "Search for missing subtitles" from the channel menu regularly. Configure how often it should do that. For the average library 6 hours minimum is recommended, to not hammer the providers too heavily
* Item age to be considered recent: The "Search for missing subtitles"-task only considers those items in the recently-added list, that are at most this old
@@ -133,6 +147,17 @@ The setting 'Subtitle folder' configures in which folder (current folder or othe
**When a subfolder (either custom or predefined) is used, the automatic scheduled refresh of Plex won't pick up your subtitles, only a manual refresh will!**
BETA: Physically Ignoring Media
-------------------------
Sometimes subtitles aren't needed or wanted for parts of your library.
When creating a file named `subzero.ignore`, `.subzero.ignore`, or `.nosz` in any of your library's folders, be it
the section itself, a TV show, a movie, or even a season, Sub-Zero will skip processing the contents of that folder.
BETA notes: This may still mean that the scheduler task for missing subtitles triggers refresh actions on those items,
but the refresh handler itself will skip those.
License
-------
The Unlicense