Script used to upscale ST:DS9 from DVD to HD using Topaz Video AI
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
""" | |
Upscale: Star Trek Deep Space Nine | |
""" | |
import sys | |
import subprocess | |
from pathlib import Path | |
if sys.platform == "win32": | |
BASE = Path("D:/Upscale") | |
WORK = Path("C:/Program Files/Topaz Labs LLC/Topaz Video AI") | |
else: | |
BASE = Path("~/Video/Upscale") | |
WORK = Path(".") | |
SRC = BASE / "Input" | |
DST = BASE / "Output" | |
EXT = ".mkv" | |
JOBS = [ | |
# Season 1 | |
# {"file": "DS9_1_1_t00.mkv", "crop": "w=702:h=572:x=12:y=2", "params": "S1"}, | |
# {"file": "DS9_1_1_t01.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_1_t02.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_2_t00.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_2_t01.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_2_t02.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_2_t03.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_3_t00.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_3_t01.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_3_t02.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_3_t03.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_4_t00.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_4_t01.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_4_t02.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_4_t03.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_5_t00.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_5_t01.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_5_t02.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
# {"file": "DS9_1_5_t03.mkv", "crop": "w=702:h=572:x=11:y=2", "params": "S1"}, | |
] | |
AI_PARAMS = { | |
"S1": "compression=0.30:details=0.13:blur=0.28:noise=0.06:halo=0.09:preblur=-0.05:blend=0.2", | |
} | |
DO_FULL = True | |
DO_SAMPLE = True | |
OVERWRITE_SAMPLES = False | |
SAMPLE_TIME = ("00:15:00", "00:00:20") # Start, Length | |
PROCESS = Path(__file__).parent / "process.txt" | |
# Colours: https://github.com/bbc/qtff-parameter-editor#video-characteristics | |
INPUT_SPEC = { | |
"sws_flags": "spline+accurate_rnd+full_chroma_int", | |
"color_trc": "2", # Unknown | |
"colorspace": "2", # Unknown | |
"color_primaries": "2", # Unknown | |
# "color_trc": "5", # Gamma 2.8 curve | |
# "colorspace": "5", # BT470BG | |
# "color_primaries": "5", # ITU-R BT.470BG | |
} | |
COMPLEX_FILTER = [ | |
"crop={crop}", | |
"scale=w=768:h=576", | |
"setsar=1", | |
"tvai_up=model=iris-1:scale=2:{params}:device=0:vram=1:instances=1", | |
"scale=w=1440:h=1080:flags=lanczos:threads=0", | |
] | |
VIDEO_ENC = { | |
# x264 | |
# "c:v": "libx264", | |
# VP9 (Very Slow) | |
# "c:v": "libvpx-vp9", | |
# "pix_fmt": "yuv420p", | |
# "row-mt": "1", | |
# "deadline": "best", | |
# "b:v": "0", | |
# AMD h264 | |
# "c:v": "h264_amf", | |
# "profile:v": "high", | |
# "pix_fmt": "yuv420p", | |
# "b:v": "0", | |
# "quality": "0", | |
# "rc": "cqp", | |
# "qp_i": "10", | |
# "qp_p": "12", | |
# "qp_b": "12", | |
# Apple ProRes LT | |
"c:v": "prores_ks", | |
"profile:v": "1", | |
"vendor": "apl0", | |
"quant_mat": "lt", | |
"bits_per_mb": "525", | |
"pix_fmt": "yuv422p10le", | |
# Apple ProRes Standard | |
# "c:v": "prores_ks", | |
# "profile:v": "2", | |
# "vendor": "apl0", | |
# "quant_mat": "hq", | |
# "bits_per_mb": "1350", | |
# "pix_fmt": "yuv422p10le", | |
} | |
AUDIO_ENC = { | |
"map": "0:a", | |
"c:a": "copy", | |
} | |
META_DATA = { | |
"map_metadata": "0", | |
"map_metadata:s:v": "0:s:v", | |
"map_metadata:s:a:0": "0:s:i:0", | |
} | |
def extendArgs(args: list[str], data: dict[str, str]) -> None: | |
args.extend(m for n in [[f"-{k}", f"{v}"] for k, v in data.items()] for m in n) | |
return | |
if DO_SAMPLE: | |
sampleDir = DST / "Samples" | |
sampleDir.mkdir(exist_ok=True) | |
for n, job in enumerate(JOBS, start=1): | |
inputFile = SRC / job["file"] | |
origFile = sampleDir / Path(f"{inputFile.stem}_original").with_suffix(EXT) | |
outputFile = sampleDir / Path(f"{inputFile.stem}_sample").with_suffix(EXT) | |
if OVERWRITE_SAMPLES or not origFile.exists(): | |
print("") | |
print(f"Creating Sample {n} of {len(JOBS)}") | |
print("="*80) | |
print(f"Original: {origFile}") | |
print(f"Output: {outputFile}") | |
args = [ | |
"ffmpeg", | |
"-ss", SAMPLE_TIME[0], "-t", SAMPLE_TIME[1], | |
"-i", str(inputFile), | |
"-map_metadata", "0", | |
"-vcodec", "copy", | |
"-acodec", "copy", | |
str(origFile) | |
] | |
command = " ".join(args) | |
print(f"Command: {command}") | |
print("") | |
print("-"*80) | |
print("") | |
origFile.unlink(missing_ok=True) | |
subprocess.run(command, cwd=WORK, shell=True) | |
if OVERWRITE_SAMPLES or not outputFile.exists(): | |
args = [ | |
"ffmpeg", | |
"-ss", SAMPLE_TIME[0], "-t", SAMPLE_TIME[1], | |
"-i", str(inputFile), | |
] | |
extendArgs(args, INPUT_SPEC) | |
extendArgs(args, META_DATA) | |
complexFilter = ",".join(COMPLEX_FILTER).format( | |
crop=job["crop"], | |
params=AI_PARAMS[job["params"]], | |
) | |
args.extend(["-filter_complex", complexFilter]) | |
extendArgs(args, VIDEO_ENC) | |
extendArgs(args, AUDIO_ENC) | |
args.append(str(outputFile)) | |
command = " ".join(args) | |
print("") | |
print("-"*80) | |
print(f"Command: {command}") | |
print("") | |
print("-"*80) | |
print("") | |
outputFile.unlink(missing_ok=True) | |
subprocess.run(command, cwd=WORK, shell=True) | |
if DO_FULL: | |
for n, job in enumerate(JOBS, start=1): | |
if PROCESS.exists() and PROCESS.read_text().strip().lower() == "stop": | |
print("") | |
print("****************************") | |
print("* Stopping upscale job ... *") | |
print("****************************") | |
print("") | |
break | |
inputFile = SRC / job["file"] | |
outputFile = DST / Path(f"{inputFile.stem}_upscale").with_suffix(EXT) | |
print("") | |
print(f"Upscaling Video {n} of {len(JOBS)}") | |
print("="*80) | |
if outputFile.exists(): | |
print(f"File Exists: {outputFile}") | |
continue | |
else: | |
print(f"Output: {outputFile}") | |
args = ["ffmpeg", "-i", str(inputFile)] | |
extendArgs(args, INPUT_SPEC) | |
extendArgs(args, META_DATA) | |
complexFilter = ",".join(COMPLEX_FILTER).format( | |
crop=job["crop"], | |
params=AI_PARAMS[job["params"]], | |
) | |
args.extend(["-filter_complex", complexFilter]) | |
extendArgs(args, VIDEO_ENC) | |
extendArgs(args, AUDIO_ENC) | |
args.append(str(outputFile)) | |
command = " ".join(args) | |
print(f"Command: {command}") | |
print("") | |
print("-"*80) | |
print("") | |
subprocess.run(command, cwd=WORK, shell=True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment