Created
July 4, 2023 05:05
-
-
Save YUKI2eN3e/9c06d5ad28551f92c4eebbfcb1d41c75 to your computer and use it in GitHub Desktop.
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
| #!/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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment