kkroening / ffmpeg-python

Python bindings for FFmpeg - with complex filtering support
Apache License 2.0
9.89k stars 885 forks source link

How to use a video file in memory and not written to disk ? and put an watermark on it, and do it asynchronously #500

Open aahnik opened 3 years ago

aahnik commented 3 years ago

Earlier I was doing it like this:


def watermark(video_file: str):
    outf = f'watered_{video_file}'
    command = f'ffmpeg -i {video_file} -i image.png -preset ultrafast -tune "fastdecode zerolatency" -filter_complex "overlay={X_OFF}:{Y_OFF}" {outf}'
    print(command)
    os.system(command)
    return outf

This function takes in the path of a video file, and puts watermark of image.png on it, and returns the path of the new watermarked video.

I want to do this using ffmpeg-python. How can I do it ?

Instead of taking using a file in disk, what if I have a video in form of a bytes datatype in memory ? How can I use that ? and output final video to bytes type

Can I do the job asynchronously ? using async await ?

mrbesen commented 3 years ago

on Linux you can create a namedpipe or a socket, to have a pseudo-file that can be read or mapped to a memory region. take a look at the pipe syscall.

aahnik commented 3 years ago

@mrbesen, please show some love with a code snippet. i don't know how to implement what you are saying

chugcup commented 3 years ago

I believe the short answer is "No" you cannot pass your bytes object directly to the ffmpeg-python pipeline. This is because this library is a wrapper around calling ffmpeg from the command line (via subprocess), which only accepts input from file paths.

What @mrbesen was hinting at is that if your goal was to avoid writing to disk, Linux named pipes are memory-mapped files that can be used as input/output from ffmpeg. You will still need to actually write your bytes data to this system file, but it will be faster/lighter than writing to a normal disk file.

Here is some blindly-written, untested code that should initialize a named pipe (FIFO) and write your contents to it before using this library

import os
import ffmpeg

MY_PIPE = '/tmp/ffmpeg.pipe'
os.mkfifo(MY_PIPE)

# Write your `bytes` object to the named pipe
with open(MY_PIPE, 'wb') as f:
    f.write(my_video_bytes)

# Now can pass to ffmpeg-python
(
    ffmpeg
    .input(MY_PIPE)
    # ... rest of it ...
    .run()
)
mrbesen commented 3 years ago

I am not quite sure how python wraps it, but with the c syscall you can buffer only 64KB with a normal configuration into a pipe. So you may need to change the size or write after ffmpeg is started.

https://stackoverflow.com/questions/4739348/is-it-possible-to-change-the-size-of-a-named-pipe-on-linux

I think you could also use a unnamed pipe (i guess that would be even faster) But you can only store 4KiB in a unnamedpipe with a normal configuration.

import os
pipe = os.pipe()

# pipe contains two file descriptors the first is for writing, the second is for reading
# there is probably a better way of opening a file discriptor for writing in python, but to figure that out, would be your part
with open("/proc/self/fd/" + str(pipe[0]), "wb") as f:
    f.write(b"hiiii\n")  # write your data

# do stuff with the second end (ex. give it to ffmpeg):
PATH = "/proc/" + str(os.getpid()) + "/fd/" + str(pipe[1])

The Cool thing about this is, that it does not leave a file behind, you may need to clean up