Last active
February 20, 2023 00:55
-
-
Save jessielw/231fbeed8e3fe0cdfcf832412a675f05 to your computer and use it in GitHub Desktop.
Python CLI script to batch deinterlace and encode a directory of MKV files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Required on system environment: | |
* mkvmerge | |
* dgindex | |
* x264 | |
Required system install: | |
* Install VapourSynth R61 or later | |
Use vsrepo to install: | |
* d2v | |
* havsfunc | |
* vsutil | |
* vivtc | |
Use CLI to run direcory of MKV's while detecting interlacing/encoding |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pvsfunc import PD2V | |
from havsfunc import QTGMC | |
import functools | |
import havsfunc | |
import vapoursynth as vs | |
core = vs.core | |
clip = PD2V("{}", verbose=False).deinterlace(kernel=functools.partial(QTGMC, FPSDivisor=2, Preset="Very Slow"), verbose=False).clip | |
clip.set_output() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import sys | |
from pathlib import Path | |
from subprocess import Popen, run, PIPE | |
from shutil import rmtree | |
import argparse | |
def get_args(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"-d", "--directory_input", help="Path to media files", required=True | |
) | |
parser.add_argument( | |
"-v", | |
"--vapoursynth_template", | |
help="Template to use for batch encoding", | |
required=True, | |
) | |
parser.add_argument( | |
"-o", "--output_path", help="Path to save media files", required=True | |
) | |
# parse args | |
return vars(parser.parse_args()) | |
def batch_process(dir_path, output_path, vapoursynth_template): | |
file_list = [x for x in Path(dir_path).glob("*.mkv")] | |
total_encodes = len(file_list) | |
# create output dir | |
Path(output_path).mkdir(exist_ok=True) | |
# parse vapoursynth template file and update vapoursynth_template variable | |
with open(Path(vapoursynth_template), "rt") as f: | |
vapoursynth_template = f.read() | |
for jobs, x in enumerate(sorted(file_list), start=1): | |
print("\n" + ("#" * 40)) | |
print("Starting job " + str(jobs) + " of " + str(total_encodes)) | |
print(str(x.name) + "\n" + ("#" * 40)) | |
# create temp dir | |
temp_dir = Path(str(Path(x).with_suffix("")) + "_tempdir") | |
Path(temp_dir).mkdir(exist_ok=True) | |
# create script | |
script_file = Path( | |
temp_dir / Path(str(Path(Path(x).name).with_suffix("")) + ".vpy") | |
) | |
vs_script(x, script_file, vapoursynth_template) | |
# run vapoursynth script | |
run(["python", script_file]) | |
# encode file | |
stats_file = script_file.with_suffix(".stats") | |
run_script( | |
script_file, | |
stats_file, | |
Path(str(Path(Path(output_path) / x.name).with_suffix("")) + ".h264"), | |
) | |
def vs_script(input_file, vpy_path, vapoursynth_template): | |
with open(vpy_path, "wt") as f: | |
f.write(vapoursynth_template.format(Path(input_file))) | |
def run_script(input_script, stats_file, output_file): | |
vs_pipe_cmd = ["vspipe", f"{input_script}", "-", "-c", "y4m"] | |
x264_1_cmd = [ | |
"x264", | |
"--demuxer", | |
"y4m", | |
"--pass", | |
"1", | |
"--bitrate", | |
"2500", | |
"--preset", | |
"veryslow", | |
"--profile", | |
"high", | |
"--level", | |
"4", | |
"--psy-rd", | |
"0.4:0", | |
"--no-fast-pskip", | |
"--aq-mode", | |
"3", | |
"--aq-strength", | |
"0.8", | |
"--vbv-maxrate", | |
"3750", | |
"--vbv-bufsize", | |
"7500", | |
"--no-mbtree", | |
"--bframes", | |
"16", | |
"--no-dct-decimate", | |
"--colorprim", | |
"bt709", | |
"--colormatrix", | |
"bt709", | |
"--transfer", | |
"bt709", | |
"--deblock", | |
"1:1", | |
"--stats", | |
f"{stats_file}", | |
"--output", | |
"NUL", | |
"-", | |
] | |
print("\n1st pass:") | |
vs_pipe_job = Popen(vs_pipe_cmd, stdout=PIPE) | |
x264_1_job = Popen(x264_1_cmd, stdin=vs_pipe_job.stdout) | |
x264_1_job.wait() | |
x264_2_cmd = [ | |
"x264", | |
"--demuxer", | |
"y4m", | |
"--pass", | |
"2", | |
"--bitrate", | |
"2500", | |
"--preset", | |
"veryslow", | |
"--profile", | |
"high", | |
"--level", | |
"4", | |
"--psy-rd", | |
"0.4:0", | |
"--no-fast-pskip", | |
"--aq-mode", | |
"3", | |
"--aq-strength", | |
"0.8", | |
"--vbv-maxrate", | |
"3750", | |
"--vbv-bufsize", | |
"7500", | |
"--no-mbtree", | |
"--bframes", | |
"16", | |
"--no-dct-decimate", | |
"--colorprim", | |
"bt709", | |
"--colormatrix", | |
"bt709", | |
"--transfer", | |
"bt709", | |
"--deblock", | |
"1:1", | |
"--stats", | |
f"{stats_file}", | |
"--output", | |
f"{output_file}", | |
"-", | |
] | |
print("\n2nd pass:") | |
vs_pipe_job_2 = Popen(vs_pipe_cmd, stdout=PIPE) | |
x264_2_job = Popen(x264_2_cmd, stdin=vs_pipe_job_2.stdout) | |
x264_2_job.wait() | |
print( | |
"\n" | |
+ ("#" * 40) | |
+ "\n" | |
+ "Output: " | |
+ str(output_file.name) | |
+ "\n" | |
+ ("#" * 40) | |
) | |
if __name__ == "__main__": | |
# keep prompt over if double-clicked or script is utilized with no args | |
try: | |
sys.argv[1] | |
except IndexError: | |
print("This is a command line program. Run this from a terminal.") | |
print("You can use '-h' to get parameter arguments") | |
input() | |
exit() | |
# parse arguments | |
parse_args = get_args() | |
# process files | |
batch_process( | |
dir_path=parse_args["directory_input"], | |
output_path=parse_args["output_path"], | |
vapoursynth_template=parse_args["vapoursynth_template"], | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment