Zulko / moviepy

Video editing with Python
https://zulko.github.io/moviepy/
MIT License
12.09k stars 1.51k forks source link

How to transform a file bytes stream with moviepy #2080

Open pokecheater opened 6 months ago

pokecheater commented 6 months ago

Hey Moviepy-Team :), I am struggeling with the issue of converting a file that is not physically present on the disk. I get that file from fastapi UploadFile and than want to scale it down in case it is a video so it will not take that much space in my database. But here is the catch (at least for me): VideoFileClip expects a str. In case I pass the file bytes directly it fails since it can not obtain the path suffix (as the error message indicates). My workaround is currently to create 2 tempfiles (one for the input and one for the output file) but that really not a nice solution since writing to disk can be considered as slow, especially for large video files.

Maybe it is my failure, but I was not able to find a proper solution in the internet, nor chat gpt or any llms gave me a working example. Can you tell me how to achieve it with io.BytesIO stream?

Thanks in advance :)


Here is some sample test code.

 import os
 from moviepy.editor import VideoFileClip

def upload_media(self, id: int, request: Request, upload_file: UploadFile = File(...)):
        file_name = upload_file.filename
        mime_type = upload_file.content_type
        file = upload_file.file

        output_file_bytes = None

        if mime_type.startswith('video/'):
            # filename = await upload_file.read_file()
            temp_filepath = os.path.join(os.getcwd(), ".temp", file_name) # f"{os.getcwd()}/.temp/{file_name}"
            with open(temp_filepath, "wb") as f:
                f.write(file.read())

            scaled_video_path = os.path.join(os.getcwd(), ".temp", file_name + "_scaled.mp4")
            clip = VideoFileClip(temp_filepath)
            # h265_clip = clip.encode_videofile("h265", fps=30, bitrate=4000000)
            resized_clip = clip.resize(width=1920, height=1080)
            resized_clip.write_videofile(
                os.path.join(os.getcwd(), ".temp", file_name + "_scaled.mp4"),
                codec="libx264",
                audio_codec="aac",
                fps=30,  # clip.fps,
                threads=4
            )

            with open(scaled_video_path, "r") as file:
                output_file_bytes = file.read()
            mime_type = "video/mp4"

            os.remove(temp_filepath)
            os.remove(scaled_video_path)

-->

pokecheater commented 6 months ago

No one has an idea? Is it not possible to use bytestreams directly instead of file paths?

SohamTilekar commented 6 months ago

Bro I Also Encounter the Same Problem

Bro Use the imageio.mimread which output the list of the NumPy Array. You Can Use it With the ImageSequenceClip It May Work In Your Case. But in My Case It Do not Work.

But in Finaly I Need to use the Temp File Module tempfile.NamedTemporaryFile Below Is the My Code I Can't delete it Because I Have an hierarchy of the Temp File But You Can delete Just Put Your Code in the with Block.

    def vid_url2vid_clip(url: str) -> VideoFileClip:
        # Send a GET request to the URL
        response = requests.get(url, stream=True)

        # Create a temporary file to store the video
        with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as f:
            # Write the video content to the temporary file
            for chunk in response.iter_content(chunk_size=1024):
                if chunk:
                    f.write(chunk)
            f.flush()
            return VideoFileClip(f.name)
SohamTilekar commented 6 months ago

I Write the Code with The Temp file Module use it Because Your code sucks if the error occur then the file will not get deleted.

But Why Are you using the Instance method You should use the static method here You do not Modify the class instance.

def upload_media(self, id: int, request: Request, upload_file): file_name = upload_file.filename mime_type = upload_file.content_type file = upload_file.file

    output_file_bytes = None

    if mime_type.startswith('video/'):
        with tempfile.NamedTemporaryFile("w+b", ) as tmp_file:
            # filename = await upload_file.read_file()
            tmp_file.write(file.read())

            scaled_video_path = os.path.join(os.getcwd(), ".temp", file_name + "_scaled.mp4")
            clip = VideoFileClip(tmp_file.name, target_resolution=(1980, 1080))

            with tempfile.NamedTemporaryFile('w+b', suffix='_scaled.mp4') as scaled_tmp_file:
                clip.write_videofile(
                    scaled_tmp_file.name,
                    codec="libx264",
                    audio_codec="aac",
                    fps=30,  # clip.fps,
                    threads=4
                )

                with open(scaled_video_path, "rb") as file: # Use the 'rb' mode for reading the file in bytes you are reading it in 'r' mode which is for read text
                    output_file_bytes = file.read()
SohamTilekar commented 6 months ago

I Used the target_resolution parameter instead of the resize Because it is significantly faster. Chake Whether video resolution is already (1980, 1080) It Will also Speed up your code and You Do not Need for tempfile at that point

keikoro commented 4 months ago

Please always include your specs like we ask for in our issue templates – MoviePy version, platform used etc. – to help pinpoint what causes your problem, thanks.