video_utils.utils package

Submodules

video_utils.utils.check_cli module

Utility to check if CLI is installed

video_utils.utils.check_cli.check_cli(cli)

Check if CLI is installed

Determine if given command line interface (CLI) program is installed on the machine.

Parameters:

cli (str) – Name of CLI to check is installed.

Returns:

Path to the CLI executable

Return type:

str

video_utils.utils.ffmpeg_utils module

FFMpeg utilities

class video_utils.utils.ffmpeg_utils.Chapter(chapter=None)

Bases: object

Represents a chapter in a video file

add_offset(offset: float, flag: int = 2) None

To adjust the start, end, or both times

Parameters:

offset (float) – Offset time in seconds

Keyword Arguments:

flag (int) – Set to: 0 - to add offset to start time, 1 - to add offset to end time, 2 - (default) add offset to start and end

Returns:

updates internal attributes

Return type:

None

base2seconds(val)

Method that converts value in time_base units to seconds

property den: int
property end: int

End time of chapter, in time base units

property end_time: float

End time of chapter in seconds

get_end(time_base=None) tuple

Method to return chapter end time in time_base and seconds units

get_start(time_base=None)

Get chapter start time in time_base and seconds units

property num: int
seconds2base(val)

Method that converts value in seconds to time_base units

property start: int

Start time of chapter, in time base units

property start_time: float

Start time of chapter in seconds

property time_base

Time base for start/end values

property title

Chapter title

to_ffmetadata()

Method that returns information in format for FFMETADATA file

class video_utils.utils.ffmpeg_utils.FFMetaData(version=1)

Bases: object

For creating a FFMetaData file for input into ffmpeg CLI

add_chapter(*args, **kwargs)

Method to add chapter marker to FFMetaData file

Parameters:
  • one (If) – Must be Chapter instance

  • three (If) – start : Start time of chapter (float or datetime.timedelta) end : End time of chapter (float or datetime.timedelta) title : Chapter title (str)

Keyword Arguments:

time_base – String of form ‘num/den’, where num and den are integers. If the time_base is missing then start/end times are assumed to be in nanosecond. Ignored if NOT three (3) inputs.

Returns:

None

add_metadata(**kwargs)

Method to add new metadata tags to FFMetaData file

Parameters:

None

Keyword Arguments:

**kwargs – Any key/value pair where key is a valid metadata tag and value is the value for the tag.

Returns:

None

save(fpath)

Method to write ffmetadata to file

Parameters:

fpath (str) – Full path of file to write to

Keyword Arguments:

None

Returns:

Returns the fpath input

Return type:

str

class video_utils.utils.ffmpeg_utils.FFmpegProgress(interval: float = 60.0, nintervals: int | None = None)

Bases: object

Monitor FFMpeg progress

Class for monitoring output from ffmpeg to determine how much time remains in the conversion.

progress(in_val)

Get progress of FFMpeg transcode

video_utils.utils.ffmpeg_utils.check_integrity(fpath: str) bool

Test the integrity of a video file.

Runs ffmpeg with null output, checking errors for ‘overread’. If overread found, then return False, else True

Parameters:

fpath (str) – Full path of file to check

Keyword Arguments:

None

Returns:

True if no overread errors, False otherwise

Return type:

bool

video_utils.utils.ffmpeg_utils.combine_mp4_files(out_file: str, *args: str) None

Combine multiple (2+) mp4 files into a single mp4 file

Parameters:
  • out_file (str) – Output (combined) file path

  • *args (str) – Any number of input file paths

Keyword Arguments:

None

Returns:

None

video_utils.utils.ffmpeg_utils.cropdetect(infile: str, video_res: tuple[int] | None, seg_len: int | float = 20, threads: int | None = None) str | None

Use FFmpeg to to detect a cropping region for video files

Parameters:
  • infile (str) – Path to input file for crop detection

  • video_res (tuple) – Tuple of ints specifying the video width/height

Keyword Arguments:
  • seg_len – Length of video, in seconds, starting from beginning to use for crop detection, default is 20 seconds

  • threads (int) – Number of threads to use when running ffmpeg to detect crop

Returns:

FFmpeg video filter in the format crop=w:h:x:y or

None if no cropping detected

video_utils.utils.ffmpeg_utils.cropdetect_cmd(infile: str, start_time: timedelta, seg_len: timedelta, threads: int | None) list[str]

Generate ffmpeg command list for crop detection

Parameters:
  • infile (str) – File to read from

  • start_time (timedelta) – Start time for segment

  • seg_len (timedelta) – Length of segment for crop detection

  • threads (int) – number of threads to let ffmpeg use

Keyword Arguments:

None.

Returns:

Command to be run using subprocess.Popen

Return type:

list

video_utils.utils.ffmpeg_utils.extract_hevc(in_file, out_file: str | None = None) str | Popen

Extract HEVC stream from file

Parameters:

in_file (str) – Input file to extract stream from

Keyword Arguments:

out_file (str) – File to write stream to. If no file specified, then a Popen object is returned with stdout=subprocess.PIPE

Returns:

Based on the out_file keyword argument, either the path

to the output file or a Popen object is returned.

Return type:

str, Popen

video_utils.utils.ffmpeg_utils.get_chapters(fpath)

Function for extract chapter information from video

Parameters:

fpath (str) – Path of file to extract chapter information from

Keyword Arguments:

None

Returns:

List of Chapter objects if chapters exist, None otherwise

video_utils.utils.ffmpeg_utils.get_content_light_level_metadata(side_data)

Extract content light level metadata

Parameters:

side_data (list) – Dicts containing HDR metadata

Returns:

dict

video_utils.utils.ffmpeg_utils.get_hdr_opts(fpath)

Function to get HDR x265 options

Parameters:

fpath (str) – Path of file to get HDR options for

Returns:

None, tuple

video_utils.utils.ffmpeg_utils.get_mastering_display_metadata(side_data)

Extract mastering display metadata

Parameters:

side_data (list) – Dicts containing HDR metadata

Returns:

dict

video_utils.utils.ffmpeg_utils.get_video_length(in_file: str) float

Returns float length of video, in seconds

video_utils.utils.ffmpeg_utils.partial_extract(in_file: str, out_file: str, start_offset: float | timedelta, duration: float | timedelta, chapter_file: str | None = None) bool

Function for extracting video segement

Parameters:
  • in_file (str) – Path of file to extract segment from

  • out_file (str) – Path of file to extract segment to

  • start_offset (float,timedelta) – Segment start time in input file. If float, units must be seconds.

  • duration (float,timedelta) – Segment duration If float, units must be seconds.

Keyword Arguments:

chapter_file (str) – Path to ffmetadata file specifying chapters for new segment

Returns:

True on successful extraction, False otherwise

Return type:

bool

video_utils.utils.ffmpeg_utils.progress(proc: Popen, interval: float = 60.0, nintervals: int | None = None) None

Determine how much time remains in the conversion.

Parameters:

proc (Popen) – A subprocess.Popen instance. The stdout of Popen must be set to subprocess.PIPE and the stderr must be set to subprocess.STDOUT so that all information runs through stdout. The universal_newlines keyword must be set to True as well

Keyword Arguments:
  • interval (float) – The update interval, in seconds, to log time remaining info. Default is sixty (60) seconds, or 1 minute.

  • nintervals (int) – Set to number of updates you would like to be logged about progress. Default is to log as many updates as it takes at the interval requested. Setting this keyword will override the value set in the interval keyword. Note that the value of interval will be used until the first log, after which point the interval will be updated based on the remaing conversion time and the requested number of updates

Returns:

Returns nothing. Does NOT wait for the process to finish so MUST

handle that in calling function

video_utils.utils.ffmpeg_utils.split_on_chapter(in_file: str, n_chapters: int | list[int]) None

Split a video file based on chapters.

The idea is that if a TV show is ripped with multiple episodes in one file, assuming all episodes have the same number of chapters, one can split the file into individual episode files.

Parameters:
  • in_file (str) – Full path to the file that is to be split.

  • n_chapters (int,list) – The number of chapters in each episode.

Keyword Arguments:

None

Returns:

Outputs n files, where n is equal to the total number

of chapters in the input file divided by n_chaps.

video_utils.utils.ffmpeg_utils.total_seconds(*args) list

Convert time strings to the total number of seconds represented by the time

Parameters:

*args – One or more time strings of format HH:MM:SS

Keyword Arguments:

None.

Returns:

Returns a numpy array of total number of seconds in time

video_utils.utils.handlers module

Specialized logging handlers

class video_utils.utils.handlers.EMailHandler(*args, **kwargs)

Bases: Handler

Logging handler for sending email

Send an email with some logging information

emit(record)

Overload emit to send email

parse_send_to(send_to)

Parse list of send-to emails

Parse the list of send-to emails from the config yaml file

Parameters:

send_to (list,tuple,str) – Emails address to send logs to

Returns:

String of comma separated emails if list could be

parsed, None otherwise

Return type:

str,None

send(subject=None)

Actually send the email

Keyword Arguments:

subject (str) – Custom subject line for the email

Returns:

True on send attempted, False on failure

Return type:

bool

set_send_level(level)

Set the level at which logs are automatically emailed. For example, if the SendLevel is set to logging.WARNING, if a log with level WARNING or higher is encountered, an email will be sent. Note that if this level is set lower than the logger level, the email my be empty.

Parameters:

level (int) – The logging level

Keyword Arguments:

None

Returns:

None

class video_utils.utils.handlers.RotatingFile(*args, **kwargs)

Bases: Thread

A class that acts like the logging.handlers.RotatingFileHander; writing data out to a file until file grows too large, then rolling over.

This class is intended to be used for stdout and stderr for calls to subprocess module routines, such as the Popen constructor. However, there are many other applications for this class.

close()

Method to clean up the pipe and logging file

fileno()

Method to get underlying file pointer; in this case pipe

run()

Overloads the run method; this method will run in own thread.

Reads data from pipe and passes to self.log.emit

Parameters:

None

Keyword Arguments:

None

Returns:

None

setFormatter(*args)

Set the formatter for the log handler

start()

Overload thread start method

video_utils.utils.handlers.init_log_file(format_dict: dict) None

Initialize new handlers given options

Given a dictionary of options for a handler, attempted to define a new handler for the package logger

Argumenst:
format_dict (dict)Information for setting up a new handler for

the package logger

Returns:

video_utils.utils.handlers.send_email(func)

Function to act as decorator to send email using EMailHandler

Parameters:

func – Function to wrap

Keyword Arguments:

None.

Returns:

Wrapped function

video_utils.utils.pid_check module

Check for already running process

Utilities for checking if a process with the same name/pid is currently running so that multiple instances of code are not running at the same time, namely the file watchdogs

video_utils.utils.pid_check.pid_running(fpath)

Check if process is already running

Check if current process is already running based on data in JSON formatted file created by pid_store()

Parameters:

fpath (str) – Full path of file to check pid information against

Keyword Arguments:

None

Returns:

True if process is already running, False otherwise

Return type:

bool

video_utils.utils.pid_check.pid_store(fpath)

Store information about the current process in a JSON formatted file

Parameters:

fpath (str) – Full path of file to write data to

Keyword Arguments:

None

Returns:

True if file created, False otherwise

Return type:

bool

video_utils.utils.subproc_pool module

Scheduler for multiple subprocesses

Classes to support scheduling of multiple subprocesses so that not too many threads/processes are consumed on machine.

class video_utils.utils.subproc_pool.PopenPool(*args, threads=None, cpulimit=None, queueDepth=None, **kwargs)

Bases: Thread

Mimic multiprocessing.Pool class, but for subprocess.Popen objects

close()

Closes the PopenPool, similar to multiprocessing.Pool.close()

Running this method will disable adding processes to the queue.

property cpulimit

Percentage of CPU allowed for each process

popen_async(*args, **kwargs)

A method to asynconously run subprocess.Popen calls

Parameters:

*args – All inputs for subprocess.Popen

Keyword Arguments:
  • threads (int) – Specify the number of threads the process will use. Default is one (1)

  • subprocess.Popen (**kwargs All keywords for) –

Returns:

A PopenPool.PopenThread instance; very similar to subprocess.Popen

instance

Note

If too many processes are already queued, this method will

block until some finish.

run()

Handles dequeuing and starting Popen processes.

property threads

Number of threads to all to run at one time

wait(timeout: int | float | None = None) bool

Method to wait for all processes in queue to finish

Parameters:

None

Keyword Arguments:

timeout (float) – Timeout in seconds

Returns:

True if queue is empty, False otherwise;

will be False on timeout

Return type:

bool

class video_utils.utils.subproc_pool.PopenThread(*args, **kwargs)

Bases: Thread

Wrapper class for subprocess.Popen that allows starting process in future

This class is designed to allow for starting a subprocess.Popen instance in the future by initializing the Popen instance within the Thread.run method. Thus, the subprocess does not start until the Thread.start method is called.

Note

The thread will run until the subprocess finishes, fails, or is killed

apply_func(func, *args, **kwargs)

Method to apply function to Popen process

Parameters:
  • func – Function to apply to process; Must accept subprocess.Popen instance as first argument

  • *args – Any other arguments to pass the func

Keyword Arguments:

**kwargs – Any keywords to pass to func

Returns:

True if function applied, False otherwise.

Return type:

bool

kill()

Kill the subprocess; see subprocess.Popen()

poll()

Poll subprocess to see if finished; see subprocess.Popen()

property returncode

Return code of subprocess; see subprocess.Popen()

run()

Overload run method

start_wait(timeout=None)

Wait for subprocess to start

Waits for global NLOCK to be acquried

Keyword Arguments:

timeout (float) – Time (in seconds) to wait for process to start. If None, will block forever.

Returns:

True if process started, False if failed or timed out

Return type:

bool

property threads

Number of threads this process requries

wait(timeout=None)

Wait for subprocess to finish; see subprocess.Popen()

video_utils.utils.subproc_pool.make_dirs(path)

Try to make all directories in input path

Parameters:

path (str) – Path to file

Keyword Arguments:

None

Returns:

True if directory(ies) created, False otherwise

Return type:

bool

video_utils.utils.update_file_names module

To update file naming convention

Function to generate hard links to existing files using new file nameing convention. Will walk through given directory, finding all files with IMDBid in name. Will then get TVDb or TMDb information for renaming.

video_utils.utils.update_file_names.movie_rename(topdir, path, metadata, imdbID, rootdir=None)

To rename movies to new convention

video_utils.utils.update_file_names.tv_rename(topdir, path, imdbID, seriesID, rootdir=None, **kwargs)

To rename TV episodes to new convention

video_utils.utils.update_file_names.update_file_names(*args, rootdir=None, dbID=None, **kwargs)

Function to iterate over Plex Library directories to rename

Parameters:

*args – A bunch of comma separated paths. These can either be top level Library directories OR individual directories within a library. If they are individual directories within a library, then must set the rootdir keyword for things to work correctly.

Keyword Arguments:
  • rootdir (str) – Set if scanning a subset of directories in a library directory. For example, if renaming files in Westworld and the args path is /path/to/TV Shows/Westworld, then should set rootdir=/path/to/TV Shows

  • dbID (str) – Force a given TV series ID

  • **kwargs – Various other keywords, including the dvdOrder key

Returns:

None

video_utils.utils.update_file_names.update_specials(season00, dbID, rootdir, **kwargs)

Update special episodes?

Module contents

Various utilities to aid in processing

Tons of different utilities to aid in the conversion/manipulation of video files.

class video_utils.utils.NLock(threads=None)

Bases: object

Semaphore-like class that allows acquire to decrement by arbitrary number

This class is designed to mimic a semaphore object, with the aquried and release methods decrementing and incrementing, respectively, an internal counter. The difference is that the acquire and release methods can be passed a ‘threads’ value to increment/decrement by a number larget than one (1). This allows locking for processes that require more than one thread to run.

Note

This is not perfect and, because of how events are ‘queued’, more threads than specified could start. For example, say 2 threads are allowed in the NLock object: code-block:

LOCK = NLock(2)

Now, image two (2) processes, each requiring one (1) thread, acquire the lock code-block:

if LOCK.acquire( threads = 1 ):
  ...process1...
if LOCK.acquire( threads = 1 ):
  ...process2...

While those are running, say a third (3rd) process that requries two (2) threads tries to get the lock code-block:

if LOCK.acquire( threads = 2 ):
  ...process3...

Now, this call to acquire() for process3 will block, but only until one (1) of the single threaded processes finish (process1 or process2). Say process1 is very long running and process2 is fairly short. In this case, process2 will finish, releasing the lock, allowing process3 to start.

The issue in this case is that the NLock object has now allowed three (3) threads to run instead of only allowing two (2).

This issue will only be encountered if acquire is called with varying thread counts.

acquire(*args, **kwargs)

Allows for n grabs to lock

Alows for n grabs to lock before it will block. For example, say n = 10, then the acquire() can be called 10 times before it will block. Just as with threading.Lock.acquire(), it will block forever unless keywords are passed

Parameters:

*args – Accepts all inputs from threading.Lock.aquire()

Keyword Arguments:
  • threads (int) – Specifies the number of ‘locks’ to acquire. default 1. When using this class to block number of processes, this is used when a process will use more than one thread.

  • **kwargs – All other keywords for threading.Lock.aquire()

Returns:

True if lock acquired, False otherwise

Return type:

bool

locked()

Returns True if locked, False otherwise

property n

Count of threads trying to acquire lock

release(threads=None)

This method acts the same as a normal threading.Lock.release()

Parameters:

None

Keyword Arguments:

threads (int) – Specifies the number of ‘locks’ to release. default 1. When using this class to block number of processes, this is used when a process will use more than one thread.

Returns:

None

property threads

Number of thread acquires allowed

video_utils.utils.isRunning(**kwargs)

Check for SIGINT or SIGTERM encountered

video_utils.utils.thread_check(val: int | None) tuple[int | None]

Check requested number of threads

Check that requested number of threads is integer type and is within allowable range

Returns:

First value is clipped number of threads to use for a proccess

and second number is maximum number of threads

Return type:

tuple