diff --git a/resources/lib/itemtypes/movies.py b/resources/lib/itemtypes/movies.py index 45f4b48a..2c5d1636 100644 --- a/resources/lib/itemtypes/movies.py +++ b/resources/lib/itemtypes/movies.py @@ -1,13 +1,26 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from logging import getLogger +import re +import os +import string from .common import ItemBase from ..plex_api import API from .. import app, variables as v, plex_functions as PF +from ..path_ops import append_os_sep LOG = getLogger('PLEX.movies') +# Tolerance in years if comparing videos as equal +VIDEOYEAR_TOLERANCE = 1 +PUNCTUATION_TRANSLATION = {ord(char): None for char in string.punctuation} +# Punctuation removed in original strings!! +# Matches '2010 The Year We Make Contact 1984' +# from '2010 The Year We Make Contact 1984 720p webrip' +REGEX_MOVIENAME_AND_YEAR = re.compile( + r'''(.+)((?:19|20)\d{2}).*(?!((19|20)\d{2}))''') + class Movie(ItemBase): """ @@ -37,9 +50,35 @@ class Movie(ItemBase): fullpath, path, filename = api.fullpath() if app.SYNC.direct_paths and not fullpath.startswith('http'): - kodi_pathid = self.kodidb.add_path(path, - content='movies', - scraper='metadata.local') + if api.subtype: + # E.g. homevideos, which have "subtype" flag set + # Homevideo directories need to be flat by Plex' instructions + library_path, video_path = path, path + else: + # Normal movie libraries + library_path, video_path, filename = split_movie_path(fullpath) + if library_path == video_path: + # "Flat" folder structure where e.g. movies lie all in 1 dir + # E.g. + # 'C:\\Movies\\Pulp Fiction (1994).mkv' + kodi_pathid = self.kodidb.add_path(library_path, + content='movies', + scraper='metadata.local') + path = library_path + else: + # Plex library contains folders named identical to the + # video file, e.g. + # 'C:\\Movies\\Pulp Fiction (1994)\\Pulp Fiction (1994).mkv' + # Add the "parent" path for the Plex library + kodi_parent_pathid = self.kodidb.add_path( + library_path, + content='movies', + scraper='metadata.local') + # Add this movie's path + kodi_pathid = self.kodidb.add_path( + video_path, + id_parent_path=kodi_parent_pathid) + path = video_path else: kodi_pathid = self.kodidb.get_path(path) @@ -105,7 +144,7 @@ class Movie(ItemBase): api.list_to_string(api.studios()), api.trailer(), api.list_to_string(api.countries()), - fullpath, + path, kodi_pathid, api.premiere_date(), api.userrating()) @@ -243,3 +282,73 @@ class Movie(ItemBase): return unique_ids.get('imdb', unique_ids.get('tmdb', unique_ids.get('tvdb'))) + + +def split_movie_path(path): + """ + Implements Plex' video naming convention for movies: + https://support.plex.tv/articles/naming-and-organizing-your-movie-media-files/ + + Splits a video's path into its librarypath, potential video folder, and + filename. + E.g. path = 'C:\\Movies\\Pulp Fiction (1994)\\Pulp Fiction (1994).mkv' + returns the tuple + ('C:\\Movies\\', + 'C:\\Movies\\Pulp Fiction (1994)\\', + 'Pulp Fiction (1994).mkv') + + E.g. path = 'C:\\Movies\\Pulp Fiction (1994).mkv' + returns the tuple + ('C:\\Movies\\', + 'C:\\Movies\\', + 'Pulp Fiction (1994).mkv') + """ + basename, filename = os.path.split(path) + library_path, videofolder = os.path.split(basename) + + clean_filename = _clean_name(os.path.splitext(filename)[0]) + clean_videofolder = _clean_name(videofolder) + + try: + parsed_filename = _parse_videoname_and_year(clean_filename) + except (TypeError, IndexError): + LOG.warn('Could not parse video path, be sure to follow the Plex ' + 'naming guidelines!! We failed to parse this path: %s', path) + # Be on the safe side and assume that the movie folder structure is + # flat + return append_os_sep(basename), append_os_sep(basename), filename + try: + parsed_videofolder = _parse_videoname_and_year(clean_videofolder) + except (TypeError, IndexError): + # e.g. no year to parse => flat structure + return append_os_sep(basename), append_os_sep(basename), filename + if _parsed_names_alike(parsed_filename, parsed_videofolder): + # e.g. + # filename = The Master.(2012).720p.Blu-ray.axed.mkv + # videofolder = The Master 2012 + # or + # filename = National Lampoon's Christmas Vacation (1989) + # [x264-Bluray-1080p DTS-2.0] + # videofolder = Christmas Vacation 1989 + return append_os_sep(library_path), append_os_sep(basename), filename + else: + # Flat movie file-stuctrue, all movies in one big directory + return append_os_sep(basename), append_os_sep(basename), filename + + +def _parsed_names_alike(name1, name2): + return (abs(name2[1] - name1[1]) <= VIDEOYEAR_TOLERANCE and + (name1[0] in name2[0] or name2[0] in name1[0])) + + +def _clean_name(name): + """ + Returns name with all whitespaces (regex "\\s") and punctuation + (string.punctuation) characters removed; all characters in lowercase + """ + return re.sub('\\s', '', name).translate(PUNCTUATION_TRANSLATION).lower() + + +def _parse_videoname_and_year(name): + parsed = REGEX_MOVIENAME_AND_YEAR.search(name) + return parsed[1], int(parsed[2]) diff --git a/resources/lib/path_ops.py b/resources/lib/path_ops.py index f0363f5d..66367f6b 100644 --- a/resources/lib/path_ops.py +++ b/resources/lib/path_ops.py @@ -27,6 +27,17 @@ KODI_ENCODING = 'utf-8' REGEX_FILE_NUMBERING = re.compile(r'''_(\d\d)\.\w+$''') +def append_os_sep(path): + """ + Appends either a '\\' or '/' - IRRELEVANT of the host OS!! (os.path.join is + dependant on the host OS) + """ + if '/' in path: + return path + '/' + else: + return path + '\\' + + def translate_path(path): """ Returns the XBMC translated path [unicode] diff --git a/resources/lib/plex_api/base.py b/resources/lib/plex_api/base.py index bfc3a11f..a764ccd3 100644 --- a/resources/lib/plex_api/base.py +++ b/resources/lib/plex_api/base.py @@ -96,6 +96,13 @@ class Base(object): """ return self.xml.get('type') + @property + def subtype(self): + """ + Returns the subtype of media, e.g. 'clip' as string or None. + """ + return self.xml.get('subtype') + @property def section_id(self): self.check_db() diff --git a/resources/lib/variables.py b/resources/lib/variables.py index 86b23cae..39724ec0 100644 --- a/resources/lib/variables.py +++ b/resources/lib/variables.py @@ -84,7 +84,7 @@ COMPANION_PORT = int(_ADDON.getSetting('companionPort')) PKC_MACHINE_IDENTIFIER = None # Minimal PKC version needed for the Kodi database - otherwise need to recreate -MIN_DB_VERSION = '2.6.8' +MIN_DB_VERSION = '3.1.1' # Supported databases - version numbers in tuples should decrease SUPPORTED_VIDEO_DB = {