Created
July 4, 2023 05:05
-
-
Save YUKI2eN3e/9c06d5ad28551f92c4eebbfcb1d41c75 to your computer and use it in GitHub Desktop.
Revisions
-
YUKI2eN3e created this gist
Jul 4, 2023 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,168 @@ #!/usr/bin/env python3.10 """ shrink-video.py Required: ffmpeg (with libx265 and libfdk_aac) set-thumbnail (a script that takes the video_filename and the thumbnail_filename in that order) mediainfo recycle (command that sends file to the Recycle Bin) Notes: Only tested on windows. Input video is assumed to be mkv and it is assumed that a webp thumbnail is present in the same folder. """ import argparse import subprocess as sp from dataclasses import dataclass from os.path import basename, dirname, exists, relpath from shlex import join as cmd_join from shlex import split as cmd_split from sys import platform, stderr @dataclass class CliArgs: file_name: str work_dir: str | None output_dir: str cleanup: bool def get_args() -> CliArgs: parser = argparse.ArgumentParser(basename(__file__)) parser.add_argument( "-f", "--file-name", help="the name of the file to shrink", required=True ) parser.add_argument( "-w", "--work-dir", help="an intermediate folder to use while converting", default=None, ) parser.add_argument( "-o", "--output-dir", help="where to save the output file", default="." ) parser.add_argument( "-c", "--cleanup", action="store_true", help="delete original file and png", default=False, ) return CliArgs(**vars(parser.parse_args())) def _fix_path(path: str) -> str: if platform == "win32": path.replace("/", "\\") if path[-1] != "\\": path = f"{path}\\" elif path[-1] != "/": path = f"{path}/" return path def _get_duration(video_filename: str) -> str: """Get fps of video using mediainfo. Args: video_filename (str): The video to get the fps of. Returns: float: The fps of the video. """ cmd = f'mediainfo "{video_filename}" | grep "Duration.*:.*$"' proc = sp.run(cmd, shell=True, capture_output=True) return " ".join( c for c in proc.stdout.splitlines()[0].decode().split() if c.isdigit() or c in ["h", "min"] ).strip() def get_base_name(file_name) -> str: return ( file_name.split(".")[0] if len(file_name.split(".")) == 2 else ".".join(file_name.split(".")[0:-1]) ) def build_cmd( base_filename: str, output_dir: str, work_dir: str | None = None, cleanup: bool = False, ) -> str: cmd = "" if work_dir is not None and work_dir != output_dir: work_dir = _fix_path(work_dir) if cleanup: cmd = f'''ffmpeg -i "{base_filename}.mkv" -c:v libx265 -vf fps=24 -c:a libfdk_aac "{work_dir}{base_filename}.mp4" & ffmpeg -i "{base_filename}.webp" "{work_dir}{base_filename}.png" & set-thumbnail "{work_dir}{base_filename}.mp4" "{work_dir}{base_filename}.png" & recycle "{base_filename}.mkv" & recycle "{work_dir}{base_filename}.png" & move "{work_dir}{base_filename}.mp4" "{_fix_path(output_dir)}{relpath(base_filename, ".")}.mp4"''' else: cmd = f'''ffmpeg -i "{base_filename}.mkv" -c:v libx265 -vf fps=24 -c:a libfdk_aac "{work_dir}{base_filename}.mp4" & ffmpeg -i "{base_filename}.webp" "{work_dir}{base_filename}.png" & set-thumbnail "{work_dir}{base_filename}.mp4" "{work_dir}{base_filename}.png" & move "{work_dir}{base_filename}.mp4" "{_fix_path(output_dir)}{relpath(base_filename, ".")}.mp4"''' else: if cleanup: cmd = f'''ffmpeg -i "{base_filename}.mkv" -c:v libx265 -vf fps=24 -c:a libfdk_aac "{base_filename}.mp4" & ffmpeg -i "{base_filename}.webp" "{base_filename}.png" & set-thumbnail "{base_filename}.mp4" "{base_filename}.png" & recycle "{base_filename}.mkv" & recycle "{base_filename}.png"''' else: cmd = f"""ffmpeg -i "{base_filename}.mkv" -c:v libx265 -vf fps=24 -c:a libfdk_aac "{work_dir}{base_filename}.mp4" & ffmpeg -i "{base_filename}.webp" "{work_dir}{base_filename}.png" & set-thumbnail "{work_dir}{base_filename}.mp4" "{work_dir}{base_filename}.png" & """ if dirname(output_dir) != dirname(base_filename): cmd = f'''{cmd} & move "{base_filename}.mp4" "{_fix_path(output_dir)}{relpath(base_filename, ".")}.mp4"''' return cmd def build_cleanup( cleanup: bool, converted_filename: str, source_filename: str, output_dir: str, ) -> str | bool: if cleanup: if ( _get_duration( f'"{_fix_path(output_dir)}{relpath(converted_filename, ".")}"' ) == _get_duration() ): return f'''recycle "{source_filename}" & recycle "{get_base_name(source_filename)}.png"''' return False def sanitize_cmd(cmd: str) -> str: return cmd_join(cmd_split(cmd)).replace("'", '"').replace('"&"', "&") def run(): args = get_args() base_filename = get_base_name(args.file_name) cmd = build_cmd( base_filename=base_filename, output_dir=args.output_dir, work_dir=args.work_dir, cleanup=args.cleanup, ) # Convert cmd = sanitize_cmd(cmd) print(cmd) sp.run(cmd, shell=True) # Cleanup if args.cleanup: converted_filename = ( f'"{_fix_path(args.output_dir)}{relpath(base_filename, ".")}.mp4"' ) if exists(converted_filename): cleanup_cmd = build_cleanup( cleanup=args.cleanup, converted_filename=converted_filename, source_filename=args.file_name, output_dir=args.output_dir, ) if type(cleanup_cmd) == bool and cleanup_cmd == False: print("Files do not match, cleanup skipped.", file=stderr) else: sp.run(sanitize_cmd(cleanup_cmd), shell=True) if __name__ == "__main__": run()