<- Back

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()'