You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

130 lines
3.6 KiB
Python

#!/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()