diff options
author | JayZed <[email protected]> | 2024-03-13 23:14:00 -0400 |
---|---|---|
committer | JayZed <[email protected]> | 2024-03-13 23:14:00 -0400 |
commit | d875dc7733c901246881325ee3a84fe5d44b10b9 (patch) | |
tree | 503593f05ce7034c011a6cd401d2acd7648837e3 /custom_libs/subliminal/video.py | |
parent | 6c6cf0716f2e413262a46d14adca4ec1d4174bc5 (diff) | |
parent | 1c25d125d3db6b5ce2b38076d179dbfddf7cc2df (diff) | |
download | bazarr-d875dc7733c901246881325ee3a84fe5d44b10b9.tar.gz bazarr-d875dc7733c901246881325ee3a84fe5d44b10b9.zip |
Merge branch 'development' of https://github.com/morpheus65535/bazarr into development
Diffstat (limited to 'custom_libs/subliminal/video.py')
-rw-r--r-- | custom_libs/subliminal/video.py | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/custom_libs/subliminal/video.py b/custom_libs/subliminal/video.py new file mode 100644 index 000000000..1264d0b84 --- /dev/null +++ b/custom_libs/subliminal/video.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +from __future__ import division +from __future__ import absolute_import +from datetime import datetime, timedelta +import logging +import os + +from guessit import guessit + +logger = logging.getLogger(__name__) + +#: Video extensions +VIDEO_EXTENSIONS = ('.3g2', '.3gp', '.3gp2', '.3gpp', '.60d', '.ajp', '.asf', '.asx', '.avchd', '.avi', '.bik', + '.bix', '.box', '.cam', '.dat', '.divx', '.dmf', '.dv', '.dvr-ms', '.evo', '.flc', '.fli', + '.flic', '.flv', '.flx', '.gvi', '.gvp', '.h264', '.m1v', '.m2p', '.m2ts', '.m2v', '.m4e', + '.m4v', '.mjp', '.mjpeg', '.mjpg', '.mkv', '.moov', '.mov', '.movhd', '.movie', '.movx', '.mp4', + '.mpe', '.mpeg', '.mpg', '.mpv', '.mpv2', '.mxf', '.nsv', '.nut', '.ogg', '.ogm', '.ogv', '.omf', + '.ps', '.qt', '.ram', '.rm', '.rmvb', '.swf', '.ts', '.vfw', '.vid', '.video', '.viv', '.vivo', + '.vob', '.vro', '.webm', '.wm', '.wmv', '.wmx', '.wrap', '.wvx', '.wx', '.x264', '.xvid') + + +class Video(object): + """Base class for videos. + + Represent a video, existing or not. + + :param str name: name or path of the video. + :param str source: source of the video (HDTV, Web, Blu-ray, ...). + :param str release_group: release group of the video. + :param str resolution: resolution of the video stream (480p, 720p, 1080p or 1080i). + :param str video_codec: codec of the video stream. + :param str audio_codec: codec of the main audio stream. + :param str imdb_id: IMDb id of the video. + :param dict hashes: hashes of the video file by provider names. + :param int size: size of the video file in bytes. + :param set subtitle_languages: existing subtitle languages. + + """ + def __init__(self, name, source=None, release_group=None, resolution=None, video_codec=None, audio_codec=None, + imdb_id=None, hashes=None, size=None, subtitle_languages=None): + #: Name or path of the video + self.name = name + + #: Source of the video (HDTV, Web, Blu-ray, ...) + self.source = source + + #: Release group of the video + self.release_group = release_group + + #: Resolution of the video stream (480p, 720p, 1080p or 1080i) + self.resolution = resolution + + #: Codec of the video stream + self.video_codec = video_codec + + #: Codec of the main audio stream + self.audio_codec = audio_codec + + #: IMDb id of the video + self.imdb_id = imdb_id + + #: Hashes of the video file by provider names + self.hashes = hashes or {} + + #: Size of the video file in bytes + self.size = size + + #: Existing subtitle languages + self.subtitle_languages = subtitle_languages or set() + + @property + def exists(self): + """Test whether the video exists""" + return os.path.exists(self.name) + + @property + def age(self): + """Age of the video""" + if self.exists: + return datetime.utcnow() - datetime.utcfromtimestamp(os.path.getmtime(self.name)) + + return timedelta() + + @classmethod + def fromguess(cls, name, guess): + """Create an :class:`Episode` or a :class:`Movie` with the given `name` based on the `guess`. + + :param str name: name of the video. + :param dict guess: guessed data. + :raise: :class:`ValueError` if the `type` of the `guess` is invalid + + """ + if guess['type'] == 'episode': + return Episode.fromguess(name, guess) + + if guess['type'] == 'movie': + return Movie.fromguess(name, guess) + + raise ValueError('The guess must be an episode or a movie guess') + + @classmethod + def fromname(cls, name): + """Shortcut for :meth:`fromguess` with a `guess` guessed from the `name`. + + :param str name: name of the video. + + """ + return cls.fromguess(name, guessit(name)) + + def __repr__(self): + return '<%s [%r]>' % (self.__class__.__name__, self.name) + + def __hash__(self): + return hash(self.name) + + +class Episode(Video): + """Episode :class:`Video`. + + :param str series: series of the episode. + :param int season: season number of the episode. + :param int episode: episode number of the episode. + :param str title: title of the episode. + :param int year: year of the series. + :param bool original_series: whether the series is the first with this name. + :param int tvdb_id: TVDB id of the episode. + :param list alternative_series: alternative names of the series + :param \*\*kwargs: additional parameters for the :class:`Video` constructor. + + """ + def __init__(self, name, series, season, episode, title=None, year=None, original_series=True, tvdb_id=None, + series_tvdb_id=None, series_imdb_id=None, alternative_series=None, **kwargs): + super(Episode, self).__init__(name, **kwargs) + + #: Series of the episode + self.series = series + + #: Season number of the episode + self.season = season + + #: Episode number of the episode + self.episode = episode + + #: Title of the episode + self.title = title + + #: Year of series + self.year = year + + #: The series is the first with this name + self.original_series = original_series + + #: TVDB id of the episode + self.tvdb_id = tvdb_id + + #: TVDB id of the series + self.series_tvdb_id = series_tvdb_id + + #: IMDb id of the series + self.series_imdb_id = series_imdb_id + + #: Alternative names of the series + self.alternative_series = alternative_series or [] + + @classmethod + def fromguess(cls, name, guess): + if guess['type'] != 'episode': + raise ValueError('The guess must be an episode guess') + + # We'll ignore missing fields. The Video instance will be refined anyway. + + # if 'title' not in guess or 'episode' not in guess: + # raise ValueError('Insufficient data to process the guess') + + + # Currently we only have single-ep support (guessit returns a multi-ep as a list with int values) + # Most providers only support single-ep, so make sure it contains only 1 episode + # In case of multi-ep, take the lowest episode (subtitles will normally be available on lowest episode number) + episode_guess = guess.get('episode', 1) + episode = min(episode_guess) if episode_guess and isinstance(episode_guess, list) else episode_guess + + return cls(name, guess.get("title", "Unknown Title"), guess.get('season', 1), episode, title=guess.get('episode_title'), + year=guess.get('year'), source=guess.get('source'), original_series='year' not in guess, + release_group=guess.get('release_group'), resolution=guess.get('screen_size'), + video_codec=guess.get('video_codec'), audio_codec=guess.get('audio_codec'), + streaming_service=guess.get("streaming_service"), other=guess.get("other"), + edition=guess.get("edition", guess.get("alternative_title"))) + + @classmethod + def fromname(cls, name): + return cls.fromguess(name, guessit(name, {'type': 'episode'})) + + def __repr__(self): + if self.year is None: + return '<%s [%r, %dx%d]>' % (self.__class__.__name__, self.series, self.season, self.episode) + + return '<%s [%r, %d, %dx%d]>' % (self.__class__.__name__, self.series, self.year, self.season, self.episode) + + +class Movie(Video): + """Movie :class:`Video`. + + :param str title: title of the movie. + :param int year: year of the movie. + :param list alternative_titles: alternative titles of the movie + :param \*\*kwargs: additional parameters for the :class:`Video` constructor. + + """ + def __init__(self, name, title, year=None, alternative_titles=None, **kwargs): + super(Movie, self).__init__(name, **kwargs) + + #: Title of the movie + self.title = title + + #: Year of the movie + self.year = year + + #: Alternative titles of the movie + self.alternative_titles = alternative_titles or [] + + @classmethod + def fromguess(cls, name, guess): + if guess['type'] != 'movie': + raise ValueError('The guess must be a movie guess') + + # We'll ignore missing fields. The Video instance will be refined anyway. + + # if 'title' not in guess: + # raise ValueError('Insufficient data to process the guess') + + alternative_titles = [] + if 'alternative_title' in guess: + alternative_titles.append(u"%s %s" % (guess['title'], guess['alternative_title'])) + + return cls(name, guess.get('title', 'Unknown Title'), source=guess.get('source'), release_group=guess.get('release_group'), + resolution=guess.get('screen_size'), video_codec=guess.get('video_codec'), other=guess.get("other"), + audio_codec=guess.get('audio_codec'), year=guess.get('year'), alternative_titles=alternative_titles, + streaming_service=guess.get("streaming_service"), edition=guess.get("edition")) + + @classmethod + def fromname(cls, name): + return cls.fromguess(name, guessit(name, {'type': 'movie'})) + + def __repr__(self): + if self.year is None: + return '<%s [%r]>' % (self.__class__.__name__, self.title) + + return '<%s [%r, %d]>' % (self.__class__.__name__, self.title, self.year) |