Skip to content

Commit 88dab34

Browse files
committed
Refactoring, cleanup, minor updates
1 parent 8b34851 commit 88dab34

File tree

2 files changed

+223
-207
lines changed

2 files changed

+223
-207
lines changed

TranscodeSession.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from datetime import datetime
2+
import json
3+
import os
4+
from pprint import pprint
5+
import shlex
6+
import signal
7+
import subprocess
8+
import sys
9+
10+
class Session():
11+
class Settings:
12+
class RF:
13+
SD = 21
14+
HD = 22
15+
FHD = 23
16+
UHD = 26
17+
18+
class ENCOPTS:
19+
SD = "ctu=32:qg-size=16"
20+
HD = "ctu=32:qg-size=32"
21+
FHD = "ctu=64:qg-size=64"
22+
UHD = "ctu=64:qg-size=64"
23+
24+
def __init__(self, file, args):
25+
signal.signal(signal.SIGINT, self.signal_handler)
26+
27+
self.args = args
28+
29+
# Get source file metadata
30+
cmd = "ffprobe -v quiet -print_format json -show_streams " + file
31+
metadata = subprocess.check_output(shlex.split(cmd)).decode("utf-8")
32+
metadata = json.loads(metadata)["streams"][0]
33+
34+
# Populate metadata-based attributes
35+
self.path = {"source": file}
36+
self.source = {"height": int(metadata["height"]), "width": int(metadata["width"]), "duration": float(metadata["duration"]), "filename": os.path.splitext(os.path.relpath(self.path["source"], "source"))[0], "filesize": os.path.getsize(self.path["source"]), "bitrate": int(metadata["bit_rate"]), "frames": int(metadata["nb_frames"]), "codec": metadata["codec_name"]}
37+
height = self.source["height"]
38+
if height < 720:
39+
resolution = "SD"
40+
elif 720 <= height < 1080:
41+
resolution = "HD"
42+
elif 1080 <= height < 2160:
43+
resolution = "FHD"
44+
elif 2160 <= height:
45+
resolution = "UHD"
46+
self.source["resolution"] = resolution
47+
48+
# Create empty attributes for dynamic session options
49+
self.encoder_quality = None
50+
self.encoder_preset = None
51+
self.preset_name = None
52+
self.encoder_options = None
53+
54+
# Construct session options and parameters
55+
self.map_options()
56+
self.file_decorator = "_RF" + str(self.encoder_quality)
57+
self.file_decorator += "_{preset}".format(preset=self.encoder_preset.capitalize())
58+
if self.args.baseline:
59+
self.file_decorator += "_Baseline"
60+
elif self.args.best:
61+
self.file_decorator += "_Best"
62+
if self.args.small:
63+
self.file_decorator += "_Small"
64+
self.path["output"] = "hevc/" + self.source["filename"] + self.file_decorator + ".mp4"
65+
self.path["log"] = "performance/" + self.source["filename"] + self.file_decorator + ".log"
66+
67+
# Verify no attributes are None
68+
self.validate()
69+
70+
# Build HandBrakeCLI command
71+
self.command = "HandBrakeCLI --encoder-preset {encoder_preset} --preset-import-file presets.json --preset {preset_name} --quality {quality} --encopts {encopts} --input {source_path} --output {output_path}".format(encoder_preset=self.encoder_preset, preset_name=self.preset_name, quality=str(self.encoder_quality), encopts=self.encoder_options, source_path=self.path["source"], output_path=self.path["output"])
72+
73+
def signal_handler(self, sig, frame):
74+
""" Delete output file if ctrl+c is caught, since file will be corrupt
75+
"""
76+
if hasattr(self, "job"):
77+
self.job.terminate()
78+
self.cleanup()
79+
sys.exit("\n\n{date}: Caught ctrl+c, aborting.\n\n".format(date=datetime.now()))
80+
81+
def cleanup(self):
82+
""" Always deletes output file, deletes log if --delete is passed from command-line
83+
"""
84+
if os.path.exists(self.path["output"]):
85+
os.remove(self.path["output"])
86+
if self.args.delete:
87+
if os.path.exists(self.path["log"]):
88+
os.remove(self.path["log"])
89+
90+
def log(self, elapsed_time, fps, compression_ratio):
91+
""" Summarizes transcode session for screen and log
92+
"""
93+
with open(self.path["log"], "w") as logfile:
94+
summary = "{elapsed_time}\n{fps} fps\n{compression_ratio}% reduction".format(elapsed_time=elapsed_time, fps=fps, compression_ratio=compression_ratio)
95+
logfile.write(summary + "\n\n" + session.args + "\n\n")
96+
pprint(vars(self), logfile)
97+
print(summary)
98+
99+
def map_options(self):
100+
""" Start with settings based on source resolution and then override defaults based on command-line arguments
101+
"""
102+
self.encoder_quality = getattr(self.Settings.RF, self.source["resolution"])
103+
self.encoder_options = getattr(self.Settings.ENCOPTS, self.source["resolution"])
104+
if self.args.best:
105+
self.preset_name = "Best"
106+
elif self.args.baseline:
107+
self.preset_name = "Baseline"
108+
else:
109+
self.preset_name = "Default"
110+
if self.args.preset:
111+
self.encoder_preset = self.args.preset.lower()
112+
else:
113+
self.encoder_preset = "slow"
114+
if self.args.quality:
115+
self.encoder_quality = self.args.quality
116+
if self.args.small:
117+
self.encoder_options += ":tu-intra-depth=3:tu-inter-depth=3"
118+
119+
def start(self):
120+
""" Starts HandBrakeCLI session and creates job attribute
121+
"""
122+
self.job = subprocess.Popen(shlex.split(self.command, posix=False)) # Posix=False to escape double-quotes in arguments
123+
124+
def summarize(self):
125+
""" Summarize transcode session before starting
126+
"""
127+
print("{date}: Starting transcode session for {source}:".format(date=str(datetime.now()), source=self.path["source"]))
128+
pprint(vars(self))
129+
print()
130+
131+
def validate(self):
132+
""" Verifies that no session attributes are null
133+
"""
134+
if any(value is None for attribute, value in self.__dict__.items()):
135+
sys.exit("FATAL: Session.validate(): found null attribute for " + self.path["source"])

0 commit comments

Comments
 (0)