Stacking frames of a video file horizontally into a single image I stumbled upon a post on Reddit a few days ago and I thought I make my own! It's a program to extract from a movie file (.mkv, .mp4, etc.) and stack them horizontally next to each other and make one picture of all the frames available in the movie file. how can I make it more pythonic, readable, and sufficient?
Also, THIS is the GitHub link, pull requests are welcome.
Also, the default frame rate (the number of frames to extract one from, to stack up in the output image) is 5.
from natsort import natsorted
from ffprobe import FFProbe
from time import sleep
from tqdm import tqdm
from PIL import Image
import cv2
import os

# get and format file path
path = input('All modules loaded, press enter to proceed...\nEnter file path to the file(w/ filename, w/out the last backslash):\n')

if not os.path.exists(path):
    print(&quot;Path is incorrect, quitting&quot;)
    exit(0)
if path[0] == &quot;~&quot;:
    os.path.expanduser(path)  # replace ~ with literal home address
meta = FFProbe(path)
# get metadata
video_duration = meta.metadata[&quot;Duration&quot;][:-3]
rate = meta.video[0].framerate
height = int(meta.video[0].width)
# one shot per 5 seconds
freq = int(rate) * 5

# info for the process bar
hours = int(video_duration[1]) * 3600
minutes = int(video_duration[3:5]) * 60
seconds = int(video_duration[-2:])
duration_seconds = hours + minutes + seconds
frames = rate * duration_seconds
output_images = frames // freq  # number of extracted images

(directory, video_name) = os.path.split(path)

dst_path = directory + &quot;/images/&quot;
os.system(&quot;mkdir &quot; + dst_path)
os.system(&quot;mkdir &quot; + dst_path + &quot;resized&quot;)
print(&quot;Destination folder created.\n&quot;)

print(&quot;Extracting frames...&quot;)
cap = cv2.VideoCapture(path)
i = 1  # current captured frame, how many 24's has already been iterated
os.system('clear')
with tqdm(total=frames) as pbar:
    while cap.isOpened():
        frame_id = cap.get(1)  # first frame in the current stream
        ret, frame = cap.read()
        if ret != True:  # if run out of frames, video finished
            break
        if frame_id % freq == 0:  # if frame# is a multiplicand of freq=24
            filename = dst_path + str(i) + &quot;.jpg&quot;
            i += 1
            cv2.imwrite(filename, frame)  # create the image
        pbar.update(1)
print(str(output_images) + &quot; images extracted successfully.&quot;)
cap.release()
del pbar

src_path = directory + &quot;/images/&quot;
dst_path = src_path + &quot;resized/&quot;

images = natsorted(os.listdir(src_path))

os.system(&quot;clear&quot;)
i = 1
max_counter = len(images) - 1  # all images count
with tqdm(total=output_images) as pbar:
    for image in images:
        if i &lt;= max_counter:  # process until all images are done
            read_path = src_path + image
            img = cv2.imread(read_path, cv2.IMREAD_UNCHANGED)
            w = 2  # convert to 2px
            h = img.shape[1]  # unchanged
            dim = (w, h)
            resized = cv2.resize(img, dim)
            filename = dst_path + str(i) + &quot;_re.jpg&quot;
            cv2.imwrite(filename, resized)  # create the image
            if i == 1:  # do only once
                print(str(img.shape) + &quot; convert to &quot; + str(dim))
            i += 1
            pbar.update(1)
        else:
            print(&quot;Finished &quot; + str(len(images) - 1) + &quot; pictures.&quot;)

images_list = natsorted(os.listdir(dst_path))

images_list = [Image.open(dst_path + x) for x in images_list]

# we have the height, user input #3
width = len(images_list) * 2  # number of small pictures, each 2px
out = Image.new(&quot;RGB&quot;, (width, height))  # create an empty image

x_offset = 0
for im in images_list:
    out.paste(im, (x_offset, 0))
    x_offset += im.size[0]

out.save(directory + &quot;/&quot; + video_name[:-4] + &quot;.jpg&quot;)
print(&quot;Cleaning Up...&quot;)
os.system(&quot;rm -rf &quot; + src_path)

print(&quot;All set, finishing now.&quot;)
Some suggestions aimed at more general issues rather than specific details in
your lines of code:

Computers are not our friends, no need to chat. Get user input from the command line, not from input(). Interactive user
input is a bad usage model for the default behavior of a script -- with rare
exceptions. Use interactivity only for special circumstances, and always
provide a command-line option to bypass that interactivity. In addition,
input() is terrible for development because it requires you (the coder) to go
into a dialogue with your script every damn time you run it. Super
annoying. One option for handling command-line input is
argparse. See the script
below for the template that I usually follow in my scripts.

But functions are our friends. Organize your code into functions. Have each function do one thing. Among
other things, this approach allows you to test each small piece of behavior as
you write it.

Either do or talk. Do not print() inside your functions that do the actual work (aside from some quite rare exceptions). Functions should take arguments, calculate something or do
something, and return some meaningful data (or None). If the larger program
requires printing ultimately, use the data returned from functions to do that
printing. But keep computation and printing in separate realms.

No magical tokens in the castle. Keep literal strings and values out of your code. Instead put them in one
or more containers. In many situations, I usually just use a Constants class
and module-level global (con), as shown below. Good practice, but easy and
low-tech.


The script template:
import argparse
import sys

class Constants:

    EXIT_OK = 0
    EXIT_FAIL = 1

    NEWLINE = '\n'
    NAMES = 'names'
    BAR = 'BAR'
    BAR_PROPAGANDA = 'Sucker, should have picked --bar!'

    OPTS_CONFIG = (
        {
            NAMES: 'path',
        },
        {
            NAMES: '-b --bar',
            'action': 'store_true',
        },
    )

con = Constants()

def main(args):
    ap, opts = parse_args(args)
    if opts.bar:
        print(con.BAR, opts)
        exit()
    else:
        print(opts)
        exit(con.EXIT_FAIL, con.BAR_PROPAGANDA)

def parse_args(args):
    ap = argparse.ArgumentParser()
    for oc in con.OPTS_CONFIG:
        kws = dict(oc)
        xs = kws.pop(con.NAMES).split()
        ap.add_argument(*xs, **kws)
    opts = ap.parse_args(args)
    return (ap, opts)

def exit(code = None, msg = None):
    code = con.EXIT_OK if code is None else code
    fh = sys.stderr if code else sys.stdout
    if msg:
        nl = con.NEWLINE
        msg = msg if msg.endswith(nl) else msg + nl
        fh.write(msg)
    sys.exit(code)

def your_function_foo(opts):
    pass

def your_function_bar(opts):
    pass

# Etc.

if __name__ == '__main__':
    main(sys.argv[1:])