posh.wiki / scriptorium!
I stole the name of this page from Nuthead.cmov
Inspired by cloc, cmov is a script that uses ffmpeg's ffprobe to count the total duration of all video files within a directory. Available on GitHub.
Show script
b'#!/bin/python3
import os, fnmatch, subprocess, math, json, sys, tqdm
from typing import List
def find_files_matching_pattern(directory: str, *patterns: List[str]):
ret: List[str] = []
tree = os.walk(directory)
if "--progress" in sys.argv or "-p" in sys.argv:
tree = tqdm.tqdm(tree)
for root, _, files in tree:
for file in files:
if any(fnmatch.fnmatch(file, pattern) for pattern in patterns):
ret.append(os.path.join(root, file))
if "-v" in sys.argv or "--verbose" in sys.argv:
print(f"Found {len(ret)} files", end="\\r")
return ret
def get_video_duration(file_path):
try:
result = subprocess.run(
[
\'ffprobe\',
\'-v\', \'error\',
\'-show_entries\', \'format=duration\',
\'-of\', \'json\',
file_path
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
metadata = json.loads(result.stdout)
duration = float(metadata[\'format\'][\'duration\'])
return math.floor(duration)
except (KeyError, ValueError, FileNotFoundError, json.JSONDecodeError) as e:
raise RuntimeError(f"Failed to get video duration: {e}")
def seconds_to_hhmmss(seconds):
hours = seconds // 3600
minutes = (seconds % 3600) // 60
seconds = seconds % 60
return f"{hours:02}:{minutes:02}:{seconds:02}"
def main():
if len(sys.argv) == 1:
print(f"Usage: {sys.argv[0]} <directory>")
exit(1)
files = find_files_matching_pattern(sys.argv[1],
"*.mp4", "*.mkv", "*.avi", "*.mov", "*.wmv", "*.flv", "*f4v", "*.mpg", "*.mpeg", "*.gif", "*.webm", "*.avi", "*.wmv", "*.yuv", "*.m4v", "*.3gp", "*.3g2")
if "-v" in sys.argv or "--verbose" in sys.argv:
print()
total: int = 0
if "--progress" in sys.argv or "-p" in sys.argv:
files = tqdm.tqdm(files)
i: int = 0
for file in files:
i += 1
total += get_video_duration(file)
if "-v" in sys.argv or "--verbose" in sys.argv:
print(f"{i}/{len(files)}: Running total: {seconds_to_hhmmss(total)} ({total}s)", end="\\r")
if "-v" in sys.argv or "--verbose" in sys.argv:
print()
if "-s" in sys.argv or "--seconds" in sys.argv:
print(total)
else:
print(seconds_to_hhmmss(total))
if __name__ == "__main__":
main()
'
500MiB-video
Are you a haver of Discord nitro but don't want to compromise your large videos' quality to get them under the 500MiB limit? No worries, this will split any single video file into as many chunks as necessary, no larger than 500MiB, without dropping a byte of quality. Available on GitHub.
Show script
b'import sys
import pathlib
import subprocess
import json
import math
def seconds_to_hhmmss(seconds: int) -> str:
hours = seconds // 3600
minutes = (seconds % 3600) // 60
seconds = seconds % 60
return f"{hours:02}:{minutes:02}:{seconds:02}"
def get_video_duration_seconds(file_path) -> int:
try:
result = subprocess.run(
[
\'ffprobe\',
\'-v\', \'error\',
\'-show_entries\', \'format=duration\',
\'-of\', \'json\',
file_path
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
metadata = json.loads(result.stdout)
duration = float(metadata[\'format\'][\'duration\'])
return math.floor(duration)
except (KeyError, ValueError, FileNotFoundError, json.JSONDecodeError) as e:
raise RuntimeError(f"Failed to get video duration: {e}")
def main():
if len(sys.argv) == 1:
print(f"Usage: {sys.argv[0]} <file>")
exit(1)
video_path = pathlib.Path(sys.argv[1])
if not video_path.is_file():
print(f"Fatal: {sys.argv[1]} is not a file.")
running_duration = 0
total_duration = get_video_duration_seconds(sys.argv[1])
next_file = sys.argv[1]
i = 0
while True:
i += 1
start_timestamp = seconds_to_hhmmss(max(0, running_duration - 5))
if (running_duration >= total_duration):
break
this_iter_output_path = f"output-{i}.mp4"
subprocess.run(["ffmpeg",
"-ss", str(start_timestamp),
"-i", next_file,
"-c:v", "hevc_nvenc",
"-fs", "512M",
this_iter_output_path])
running_duration += get_video_duration_seconds(this_iter_output_path)
if __name__ == "__main__":
main()'