How to pass local video to st.file_uploader()

I have code working for streaming video. I got nice code from other users in the forum. The use-case I am struggling with is how to upload a local file.

In a method I have the following:

@st.cache(allow_output_mutation=True)
def get_cap(uploaded_file):
    return cv2.VideoCapture(uploaded_file)

uploaded_file = st.file_uploader("Choose a video...", type=["mp4", "mpeg"])
if uploaded_file is not None:
    return uploaded_file

This returns an object of type _io.BytesIO

From this I want to pass it to openCV in the usual manner.

while True:
    image = get_cap(uploaded_file).read()
    try:
        image = cv2.resize(image, None, fx=scaling_factorx, fy=scaling_factory, interpolation=cv2.INTER_AREA)
        # do other video stuff
    except:
        break

This is the error I get:

UnhashableType: Cannot hash object of type _io.BytesIO

While caching some code, Streamlit encountered an object of type _io.BytesIO. You’ll need to help Streamlit understand how to hash that type with the hash_funcs argument. For example:


@st.cache(hash_funcs={_io.BytesIO: my_hash_func})
def my_func(...):
    ...
Please see the hash_funcs documentation for more details.

How can I upload and run processes on a local video file in streamlit?

What’s the issue you’re hitting? Can you show an error message?

I’m not quite sure I see why there’s an infinite loop there, it looks like if it succeeds in loading it’ll loop forever?

1 Like

I edited the question to make clearer.

1 Like

Hey @theholymath :wave:,

What Streamlit version are you running? If not currently on 0.59.0, any chance you could update and see if you’re still having the issue? BytesIO is now natively supported.

1 Like

Okay that took care of the unhashable type error. I was on 0.55.0. Now I get the following:

Loading in function <_io.BytesIO object at 0x10a846e30>
OpenCV: Couldn't read video stream from file "<_io.BytesIO object at 0x10a846e30>"

Cv2.videocapture looks like in the docs it takes a filename, not a file object. You could save it to disk and pass in the filename.

This almost works but it seems there may be something going on with the caching and playing the video while it is being written. I can write it to disk but then the .read() method breaks for some reason. I will try and figure this out. May need to pause any processing until the video is uploaded?

The video should be uploaded when you have the bytesio, are you closing the file? I’d suggest writing to disk and returning a filename all inside a cached function.

Here is a complete proof of concept. I expect that I can upload a video and it should play. I should be able to upload the same video if I want.

What is happening now is it will play the first time I upload it. If I try to upload another file the resize errors out. It returns an image of size 0. Sorry the code is a bit sloppy, thanks!

import streamlit as st
import io
import cv2


st.title("Play Uploaded File")

uploaded_file = st.file_uploader("Choose a video...", type=["mp4"])
temporary_location = False

if uploaded_file is not None:
    g = io.BytesIO(uploaded_file.read())  ## BytesIO Object
    temporary_location = "testout_simple.mp4"

    with open(temporary_location, 'wb') as out:  ## Open temporary file as bytes
        out.write(g.read())  ## Read bytes into file

    # close file
    out.close()


@st.cache(allow_output_mutation=True)
def get_cap(location):
    print("Loading in function", str(location))
    video_stream = cv2.VideoCapture(str(location))

    # Check if camera opened successfully
    if (video_stream.isOpened() == False):
        print("Error opening video  file")
    return video_stream


scaling_factorx = 0.25
scaling_factory = 0.25
image_placeholder = st.empty()

if temporary_location:
    while True:
        # here it is a CV2 object
        video_stream = get_cap(temporary_location)
        # video_stream = video_stream.read()
        ret, image = video_stream.read()
        if ret:
            image = cv2.resize(image, None, fx=scaling_factorx, fy=scaling_factory, interpolation=cv2.INTER_AREA)
        else:
            print("there was a problem or video was finished")
            cv2.destroyAllWindows()
            video_stream.release()
            break
        # check if frame is None
        if image is None:
            print("there was a problem None")
            # if True break the infinite loop
            break

        image_placeholder.image(image, channels="BGR", use_column_width=True)

        cv2.destroyAllWindows()
    video_stream.release()


    cv2.destroyAllWindows()

You’re writing the file to the disk each time, but caching the read.

So each time you’re writing the video to disk and always returning the same cached object, no matter what video you upload.

However that returned video stream is mutated, particularly when you read it. After the read you’re at the end of the video.

That’s what I think is happening anyway.