Created
October 1, 2023 16:42
-
-
Save hrsmwithoutspook/a17ff0e8c95be7eb1bc13c1ef9301609 to your computer and use it in GitHub Desktop.
New keyframe generator script (using vpy)
This file contains hidden or 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 argparse | |
import pathlib | |
import os | |
import os.path | |
from collections import deque | |
import vapoursynth as vs | |
from vapoursynth import core | |
from vstools import get_w | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
'--use-scxvid', '-sc', action='store_true', | |
help="Use Scxvid instead of WWXD to detect scene changes." | |
) | |
parser.add_argument( | |
'--use-slices', '-sl', action='store_true', | |
help="When using Scxvid, speeds things up at the cost of differences in scene detection." | |
) | |
parser.add_argument( | |
'--xvid', '-x', action='store_true', | |
help='Use Sushi compatible (pseudo-XviD 2pass stat file) format.' | |
) | |
parser.add_argument( | |
'--height', '-dh', default=0, type=int, | |
help='Height the clip is scaled to. 0 to process at native res.' | |
) | |
parser.add_argument( | |
'--out-file', '-o', default='null', type=str, | |
help="The file to write scene changes to (Aegisub format); defaults to the same directory as the input video file." | |
) | |
parser.add_argument( | |
'clip', | |
help="The input video file." | |
) | |
args = parser.parse_args() | |
def resize_clip(clip): | |
if args.height != 0: | |
width = get_w(args.height, clip.width / clip.height) | |
clip = core.resize.Bilinear(clip, width, args.height, format=vs.YUV420P8) # speed up the analysis by resizing first | |
return clip | |
def make_keyframes_filename(filename, output_file): | |
if output_file == "null": | |
extlen = filename[::-1].find(".") + 1 | |
return filename[:len(filename) - extlen] + "_keyframes.txt" | |
else: | |
return output_file | |
def make_keyframes(clip, use_scxvid): | |
if use_scxvid: | |
clip = core.scxvid.Scxvid(clip, use_slices=args.use_slices) | |
else: | |
clip = core.wwxd.WWXD(clip) | |
keyframes = {} | |
done = 0 | |
def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame: | |
nonlocal done | |
keyframes[n] = f.props._SceneChangePrev if use_scxvid else f.props.Scenechange # type: ignore | |
done += 1 | |
percent = float(int(10000 * done // clip.num_frames) / 100) | |
if done % (clip.num_frames // 27) == 0: | |
print("Detecting keyframes... {}/{} {:.2f}%".format(done, clip.num_frames, percent)) | |
return f | |
deque(clip.std.ModifyFrame(clip, _cb).frames(close=True), 0) | |
print("Done detecting keyframes.\n") | |
return [n for n in range(clip.num_frames) if keyframes[n]] | |
def save_keyframes(filename, keyframes, sushi): | |
with open(filename, "w") as f: | |
if sushi: | |
f.write("# XviD 2pass stat file\n") | |
f.write("\n\n") | |
else: | |
f.write("# keyframe format v1\n") | |
f.write("fps 0\n") | |
f.write("".join(f"{n}\n" for n in keyframes)) | |
kffilename = make_keyframes_filename(args.clip, args.out_file) | |
if hasattr(core, 'lsmas'): | |
clip = core.lsmas.LWLibavSource(args.clip) | |
elif hasattr(core, 'bs'): | |
clip = core.bs.VideoSource(args.clip) | |
elif hasattr(core, 'ffms2'): | |
clip = core.ffms2.Source(args.clip) | |
else: | |
raise CustomRuntimeError( | |
'You need a source filter! Install one of:\n' | |
'\tlsmash - https://github.com/AkarinVS/L-SMASH-Works\n' | |
'\tbs - https://github.com/vapoursynth/bestsource\n' | |
'\tffms2 - https://github.com/FFMS/ffms2' | |
) | |
print("Generating keyframes for {}".format(os.path.basename(args.clip))) | |
clip = resize_clip(clip) | |
keyframes = make_keyframes(clip, args.use_scxvid) | |
save_keyframes(kffilename, keyframes, args.xvid) | |
print("Written keyframes file to {}".format(os.path.basename(kffilename))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment