Eager loading of video info, fix video scaling
This commit is contained in:
18
jobdef.py
18
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 {
|
||||
|
||||
110
minfo.py
110
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
|
||||
self.display_width = self.resolution.height * self.display_aspect_ratio.ratio
|
||||
self.pixel_aspect_ratio = self.display_aspect_ratio
|
||||
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)
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user