Compare commits

..

No commits in common. '0526512cb710a75739f92e02dfb08f3ff6104d72' and 'f9ad8c053a50449e75cb2ffa9dc21d773b94a78d' have entirely different histories.

1
.gitignore vendored

@ -140,4 +140,3 @@ cython_debug/
# Local stuff
/config_local.py
__*.md

@ -10,13 +10,6 @@
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "Python: Main",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/main.py",
"console": "integratedTerminal"
}
]
}

@ -1,6 +1,4 @@
from pathlib import Path
import re
from typing import Optional
class Config:
QUEUE_FILE = "queue.json"
@ -8,5 +6,3 @@ class Config:
BASE_PATH = Path("/path/to/videos")
DESTINATION_PATH = Path("/where/to/save/result")
LOW_BITRATE_THRESHOLD = 550
FILENAME_REGEX: Optional[re.Pattern] = None
OVERWRITE: bool = True

@ -1,4 +1,3 @@
from typing import Optional
from minfo import MediaInfo
from pathlib import Path
@ -117,22 +116,9 @@ 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.__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
self.audio_track = AudioTrack(self.mediainfo)
def as_dict(self):
return {

@ -20,9 +20,6 @@ extensions = {
jobs = []
for dir, dirs, files in os.walk(str(Config.BASE_PATH)):
for file in files:
if Config.FILENAME_REGEX:
if not Config.FILENAME_REGEX.match(file):
continue
parts = file.rsplit(".", 1)
if len(parts) == 2:
extension = parts[1].lower()
@ -31,8 +28,6 @@ for dir, dirs, files in os.walk(str(Config.BASE_PATH)):
relative_path = current_path.relative_to(Config.BASE_PATH)
print(relative_path / file, end=" ")
item = QueueItem(Path(dir) / file, Config.DESTINATION_PATH / relative_path)
if not Config.OVERWRITE and item.task.destination.exists():
continue
info = item.task.mediainfo
if info.codec == 'HEVC':
print("")

@ -22,7 +22,6 @@ 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})"
@ -30,15 +29,11 @@ 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})"
@ -46,8 +41,6 @@ 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]):
@ -109,29 +102,17 @@ class MenuInfo:
class MediaInfo:
def __init__(self):
self.path: Path
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"]
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
@ -142,61 +123,84 @@ class MediaInfo:
elif track["@type"] == "Menu":
self.menu_data = track
self.determine_information_properties()
self.determine_aspect_properties()
# print(json.dumps(self.video, indent=2))
def determine_information_properties(self):
self.codec = self.video["Format"]
@property
def codec(self) -> str:
return 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:
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))
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))
@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"])
self.duration = str(timedelta(seconds=seconds))
if not self.duration:
raise Exception("No duration for video " + str(self.path))
td = str(timedelta(seconds=seconds))
return td # f"{td:0>8}"
raise Exception("No duration for video " + str(self.path))
def determine_aspect_properties(self):
@property
def resolution(self) -> Resolution:
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:
if -0.01 <= ratio[0] - self.resolution.ratio <= 0.01:
frac = Fraction(self.resolution.width, self.resolution.height)
res = self.resolution
if -0.01 <= ratio[0] - res.width / res.height <= 0.01:
frac = Fraction(res.width, res.height)
else:
new_width = round(self.resolution.height * ratio[0])
frac = Fraction(new_width, self.resolution.height)
new_width = round(res.height * ratio[0])
frac = Fraction(new_width, res.height)
ratio = (frac.numerator, frac.denominator)
self.display_aspect_ratio = Ratio(x=ratio[0], y=ratio[1])
return Ratio(x=ratio[0], y=ratio[1])
self.keep_display_aspect = 0.95 < self.resolution.ratio / self.display_aspect_ratio.ratio < 1.05
@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
if not self.keep_display_aspect:
self.display_width = self.resolution.height * self.display_aspect_ratio.ratio
self.pixel_aspect_ratio = self.display_aspect_ratio
display_width = self.resolution.height * ratio.x / ratio.y
else:
self.display_width = float(self.resolution.width)
self.pixel_aspect_ratio = Ratio(
self.display_aspect_ratio.x,
self.resolution.width
)
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)
@property
def output_bitrate(self) -> int:

@ -1,17 +0,0 @@
from minfo import MediaInfo, Ratio, Resolution
def test_square_source():
info = MediaInfo()
info.general = {
}
info.video = {
"Width": 576,
"Height": 576,
"DisplayAspectRatio": 1.778
}
info.determine_aspect_properties()
assert info.resolution == Resolution(576, 576)
assert info.pixel_aspect_ratio == Ratio(16, 9)
Loading…
Cancel
Save