#!/usr/bin/env python3 # call pyvideothumbnailer for every video file found # # calculates rows based on the duration of the video import math import os import subprocess import click import mimetypes from addict import Dict from pymediainfo import MediaInfo def is_video_file(video_file_path): type_ = mimetypes.guess_type(video_file_path)[0] return type_ is not None and type_.startswith("video/") def find_video_files(directory): # Recursively walk through directory for dirpath, dirs, files in os.walk(directory): for filename in files: filename_path = os.path.join(dirpath, filename) if is_video_file(filename_path): yield os.path.abspath(filename_path) def get_video_duration(video_file_path): # Get media information media_info = MediaInfo.parse(video_file_path) # Iterate through tracks in the media file for track in media_info.tracks: # If the track type is video if track.track_type == "Video": # Get duration and return it duration_str = track.duration duration_sec = float(duration_str) / 1000 # Convert to seconds return duration_sec # Return None if no video track found return None def call_thumbnailer(video_file, width, columns, rows, *, skip_seconds=10): # Call external thumbnailer program subprocess.run( [ "pyvideothumbnailer", "--width", str(width), "--columns", str(columns), "--rows", str(rows), "--jpeg-quality", str(90), "--header-font", config.font, "--skip-seconds", str(skip_seconds), video_file, ] ) def process(searchdir): video_files = find_video_files(searchdir) for video_file in video_files: # Skip if dead symlink if config.skip_dead_symlinks and not os.path.exists(video_file): continue # Skip if .jpg file already exists thumbnails_file = video_file + ".jpg" if os.path.lexists(thumbnails_file): if config.verbose: print(f"Skipping: {video_file} (thumbnails already exist)") continue try: duration = get_video_duration(video_file) if duration is not None: rows = math.ceil(duration / config.every / config.columns) skip_seconds = 10 if duration < 60: skip_seconds = 0 # Call the thumbnailer call_thumbnailer( video_file, config.width, config.columns, rows, skip_seconds=skip_seconds, ) except (TypeError, UnicodeEncodeError, FileNotFoundError) as e: print(f"Error for {video_file}: {e}") config = Dict() @click.command() @click.argument("searchdirs", nargs=-1) @click.option("-v", "--verbose", is_flag=True, default=False) @click.option("--skip-dead-symlinks/--no-skip-dead-symlinks", default=True) def main(searchdirs, verbose, skip_dead_symlinks): """Run pyvideothumbnailer for every video file found""" config.verbose = verbose config.skip_dead_symlinks = skip_dead_symlinks config.width = 1600 config.columns = 4 # pyvideothumbnailer default config.every = 5 * 60 # seconds config.font = "DejaVuSansMono.ttf" if not searchdirs: searchdirs = ["."] for searchdir in searchdirs: process(searchdir) if __name__ == "__main__": main()