From 0526512cb710a75739f92e02dfb08f3ff6104d72 Mon Sep 17 00:00:00 2001 From: Alexandru Pisarenco Date: Sat, 9 Jan 2021 03:08:36 +0100 Subject: [PATCH] Eager loading of video info, fix video scaling --- jobdef.py | 18 ++++++++- minfo.py | 110 ++++++++++++++++++++++++++---------------------------- 2 files changed, 69 insertions(+), 59 deletions(-) diff --git a/jobdef.py b/jobdef.py index 0b9f0ae..2a4ccb9 100644 --- a/jobdef.py +++ b/jobdef.py @@ -1,3 +1,4 @@ +from typing import Optional from minfo import MediaInfo from pathlib import Path @@ -116,9 +117,22 @@ class Task: def __init__(self, path: Path, destination: Path): self.path = path self.destination = destination / (path.name.rsplit(".", 1)[0] + '.m4v') - self.mediainfo = MediaInfo(self.path) self.allowed_passthru_options = AllowedPassthruOptions() - self.audio_track = AudioTrack(self.mediainfo) + self.__mediainfo: Optional[MediaInfo] = None + self.__audio_track: Optional[AudioTrack] = None + + @property + def mediainfo(self): + if not self.__mediainfo: + self.__mediainfo = MediaInfo() + self.__mediainfo.load_file(self.path) + return self.__mediainfo + + @property + def audio_track(self): + if not self.__audio_track: + self.__audio_track = AudioTrack(self.mediainfo) + return self.__audio_track def as_dict(self): return { diff --git a/minfo.py b/minfo.py index a17dff6..2ab5db0 100644 --- a/minfo.py +++ b/minfo.py @@ -22,6 +22,7 @@ class Resolution: def __init__(self, width: int, height: int): self.width = width self.height = height + self.ratio = self.width / self.height def __repr__(self) -> str: return f"Resolution(width={self.width},height={self.height})" @@ -29,11 +30,15 @@ class Resolution: def __str__(self) -> str: return f"{self.width}x{self.height}" + def __eq__(self, o: object) -> bool: + return self.width == o.width and self.height == o.height + class Ratio: def __init__(self, x: int, y: int): self.x = x self.y = y + self.ratio = x / y def __repr__(self) -> str: return f"Ratio(x={self.x},y={self.y})" @@ -41,6 +46,8 @@ class Ratio: def __str__(self) -> str: return f"{self.x}x{self.y}" + def __eq__(self, o: object) -> bool: + return self.ratio == o.ratio class AudioInfo: def __init__(self, audio_dict: Dict[str, str]): @@ -102,17 +109,29 @@ class MenuInfo: class MediaInfo: - def __init__(self, path: Path): - self.path = path - procinfo = Popen([Config.MEDIAINFO_BINARY, path, "--Output=JSON"], stdout=PIPE) - stdout, _ = procinfo.communicate() - data = json.loads(stdout.decode('utf-8'))["media"]["track"] - + def __init__(self): + self.path: Path self.general: Optional[Dict[str, Any]] = None self.video: Optional[Dict[str, Any]] = None self.audio: List[Dict[str, Any]] = [] self.menu_data: Optional[Dict[str, Any]] = None + self.codec: str + self.bitrate: int + self.framerate: float + self.duration: str + + self.resolution: Resolution + self.display_aspect_ratio: Ratio + self.keep_display_aspect: bool + self.display_width: float + self.pixel_aspect_ratio: Ratio + + def load_file(self, path: Path): + self.path = path + procinfo = Popen([Config.MEDIAINFO_BINARY, path, "--Output=JSON"], stdout=PIPE) + stdout, _ = procinfo.communicate() + data = json.loads(stdout.decode('utf-8'))["media"]["track"] for track in data: if track["@type"] == "General": self.general = track @@ -123,84 +142,61 @@ class MediaInfo: elif track["@type"] == "Menu": self.menu_data = track - # print(json.dumps(self.video, indent=2)) + self.determine_information_properties() + self.determine_aspect_properties() - @property - def codec(self) -> str: - return self.video["Format"] + def determine_information_properties(self): + self.codec = self.video["Format"] - @property - def bitrate(self) -> int: try: bitrate = self.video["BitRate"] except KeyError: bitrate = self.general["OverallBitRate"] + self.bitrate = int(bitrate) // 1000 - ibitrate = int(bitrate) // 1000 - return ibitrate - - @property - def framerate(self) -> float: keys = ["FrameRate_Maximum", "FrameRate", "FrameRate_Nominal"] for key in keys: if key in self.video: - return float(self.video[key]) - print(json.dumps(self.general, indent=2)) - print(json.dumps(self.video, indent=2)) - raise Exception("No frame rate for video " + str(self.path)) + self.framerate = float(self.video[key]) + if not self.framerate: + print(json.dumps(self.general, indent=2)) + print(json.dumps(self.video, indent=2)) + raise Exception("No frame rate for video " + str(self.path)) - @property - def duration(self) -> str: places_to_look = [self.video, self.general] for place in places_to_look: if "Duration" in place: seconds = float(place["Duration"]) - td = str(timedelta(seconds=seconds)) - return td # f"{td:0>8}" - raise Exception("No duration for video " + str(self.path)) + self.duration = str(timedelta(seconds=seconds)) + if not self.duration: + raise Exception("No duration for video " + str(self.path)) - @property - def resolution(self) -> Resolution: + def determine_aspect_properties(self): width = int(self.video["Width"]) height = int(self.video["Height"]) + self.resolution = Resolution(width=width, height=height) - return Resolution(width=width, height=height) - - @property - def display_aspect_ratio(self) -> Ratio: ratio = [float(x) for x in str(self.video["DisplayAspectRatio"]).split(':')] if len(ratio) != 2: - res = self.resolution - if -0.01 <= ratio[0] - res.width / res.height <= 0.01: - frac = Fraction(res.width, res.height) + if -0.01 <= ratio[0] - self.resolution.ratio <= 0.01: + frac = Fraction(self.resolution.width, self.resolution.height) else: - new_width = round(res.height * ratio[0]) - frac = Fraction(new_width, res.height) + new_width = round(self.resolution.height * ratio[0]) + frac = Fraction(new_width, self.resolution.height) ratio = (frac.numerator, frac.denominator) - return Ratio(x=ratio[0], y=ratio[1]) + self.display_aspect_ratio = Ratio(x=ratio[0], y=ratio[1]) - @property - def keep_display_aspect(self) -> bool: - ratio = self.display_aspect_ratio - resolution = self.resolution - return 0.95 < (resolution.width / resolution.height) / (ratio.x / ratio.y) < 1.05 - - @property - def display_width(self) -> float: - ratio = self.display_aspect_ratio + self.keep_display_aspect = 0.95 < self.resolution.ratio / self.display_aspect_ratio.ratio < 1.05 if not self.keep_display_aspect: - display_width = self.resolution.height * ratio.x / ratio.y - else: - display_width = float(self.resolution.width) - return display_width - - @property - def pixel_aspect_ratio(self) -> Ratio: - if self.keep_display_aspect: - return Ratio(1.0, 1.0) + self.display_width = self.resolution.height * self.display_aspect_ratio.ratio + self.pixel_aspect_ratio = self.display_aspect_ratio else: - return Ratio(self.display_aspect_ratio.x, self.resolution.width) + self.display_width = float(self.resolution.width) + self.pixel_aspect_ratio = Ratio( + self.display_aspect_ratio.x, + self.resolution.width + ) @property def output_bitrate(self) -> int: