Skip to content

Instantly share code, notes, and snippets.

@jessielw
Last active February 20, 2023 00:55
Show Gist options
  • Save jessielw/231fbeed8e3fe0cdfcf832412a675f05 to your computer and use it in GitHub Desktop.
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
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
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()
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