Manipulating image output from shell in python without storing external file - python

I am trying to store an image that is the result of ffmpeg.
Using this command, I have frame.png as an external file output:
ffmpeg -flags2 +export_mvs -i video.avi -vf 'select=gte(n\,200),codecview=mv=pf+bf+bb' -vframes 1 frame.png
I want to be able to load the frame.png directly into python, maybe using openCV but without saving it in the computer.
I thought of something like this:
cmd = "ffmpeg -flags2 +export_mvs -i video.avi -vf 'select=gte(n\,200),codecview=mv=pf+bf+bb' -vframes 1 frame.png"
img = cv.imread(sp.Popen(cmd, shell=True, stdout = sp.PIPE, stderr = sp.PIPE).communicate()[0])
But I get an error:
TypeError: bad argument type for built-in operation
Any clue how to do this? The idea is, no frame.png should be generated as a file.

You can set the output file as /dev/stdout (you might need to specify the output format with -f)
Then you redirect your output to your python script like so
ffmpeg options /dev/stdout | python your_script.py
Then you can read this question to see how you can read an image from a file object. Just replace StringIO with sys.stdin

Related

How do I integrate the FFMPEG commands with Python Script?

I am trying to extract the frames when the scene changes in an .mp4 video.
The package that I am using is FFMPEG.
FFMPEG predominantly works on the CLI and I am trying to integrate it with Python3.x
The command I am using in the CLI is:
ffmpeg -i {0} -vf "select=gt(scene\,0.5), scale=640:360" -vsync vfr frame%d.png
The output comes out just fine with the CLI execution.
But I want to use same command in a Python script, how do I do that and what should be the code?
Being an amateur in the field, currently grappling with this!
You could execute that command from Python via subprocess module, of course, but it would better to use library like https://github.com/kkroening/ffmpeg-python
I would recommend PyAV. it's a proper wrapper around ffmpeg's libraries.
the other mentioned packages use the "subprocess" approach, which is limited and inefficient. these libraries may be more convenient than plain ffmpeg APIs.
Thanks for the help!
This is the snippet of code I'm currently using and it gives the results as I require.
I have added a functionality for timestamp generation of the frames in addition to the frame formation using scene change detection
===========================================================================
> # FFMPEG Package call through script
> # need to change the location in the cmd post -vsync vfr to the location where the frames are to be stored
> # the location should be same as where the videos are located
============================================================================
inputf = []
for filename in os.listdir(path):
file= filename.split('.')[0] # Splits the file at the extension and stores it without .mp4 extension
input_file = path + filename
inputf.append(input_file) # Creates a list of all the files read
for x in range (0, len(inputf)):
cmd = f'ffmpeg -i {inputf[x]} -filter_complex "select=gt(scene\,0.2), scale=640:360, metadata=print:file=time_{file}.txt" -vsync vfr {path where the videos are located}\\{file}_frame%d.jpg'
os.system(cmd)
x=x+1
print("Done") # Takes time will loop over all the videos

How do I copy a PIL picture to clipboard

I'm working with a canvas in Tkinter, and I'm trying to copy the contents of that canvas to the clipboard.
I am able to solve this by creating a file, and then copy the content of that file into the clipboard with xclip -selection clipboard -t image/png -i temp_file.png, but I want to use a buffer instead, allowing the user to copy it directly to the clipboard without touching the filesystem of the user.
To use xclip's input function, I require to give a filename, and not a string.
I can circumvent this with echo/cat as cat temp_file.png < echo | xclip -selection clipboard -t image/png -i or cat file_name.png | xclip -selection clipboard -t image/png -i in the bash-line.
I have successfully been able to use the buffer to store the canvas, as such:
memory = io.BytesIO()
img.save(memory, format="png")
return memory
and can save the picture to a file as this:
img = pil.Image.open(memory)
img.save("file_name.png", format="png")
As far as I have understood, from pil's documentation, pil.Image.open is the same as the built-in open() function. Nevertheless, when trying to use open() to read the file, it claims BytesIO is not a valid file. Not a major problem, I guess.
I can read the contents of the buffer with memory.getvalue(), and then strip the surrouding b'...' with [2:-1]. I then replace all the ' in the string with \', so I can surround it with single quotation marks, and end up echoing said string with the command I used on a file earlier.
img_data = str(img_memory.getvalue())[2:-1]
img_data = img_data.replace("'", "\'")#.replace("`", "\`")
img_data = "'" + img_data + "'"
The output of cat file_name.png is pretty much the same as the output of the string I'm giving echo in my subprocess, but the following doesn't seem to do the job for me, as my clipboard remains untouched:
bash_cmd = f"echo -n {img_data} | xclip -selection clipboard -t image/png -i"
p = sp.Popen(bash_cmd, shell=True, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
but
bash_cmd = f"xclip -selection clipboard -t image/png -i file_name.png"
p = sp.Popen(bash_cmd, shell=True, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
does work.
tl;dr
What am I doing wrong? Am I altering the contents of the file, and creating a corrupt image, which is then copied?
How can I properly output the contents of a io.BufferIO into my clipboard (mainly on Unix/Linux, but also on MacOS/Windows)?
I managed to copy the image data with xclip from python by adapting the answer from Can I pipe a io.BytesIO() stream to subprocess.popen() in Python?. No need to use echo here, but just write the content of memory to the process stdin:
memory = io.BytesIO()
img.save(memory, format="png")
output = subprocess.Popen(("xclip", "-selection", "clipboard", "-t", "image/png", "-i"),
stdin=subprocess.PIPE)
# write image to stdin
output.stdin.write(memory.getvalue())
output.stdin.close()
As for a more multi-platform solution, I found the package klembord which works on Linux and Windows. The following command copies the picture to the clipboard (tested in Linux only):
import klembord
klembord.set({"image/png": memory.getvalue()})

python subprocess with ffmpeg give no output

I m want to extract the scene change timestamp using the scene change detection from ffmpeg. I have to run it on a few hundreds of videos , so i wanted to use a python subprocess to loop over all the content of a folder.
My problem is that the command that i was using for getting these values on a single video involve piping the output to a file which seems to not be an option from inside a subprocess call.
this is my code :
p=subprocess.check_output(["ffmpeg", "-i", sourcedir+"/"+name+".mpg","-filter:v", "select='gt(scene,0.4)',showinfo\"","-f","null","-","2>","output"])
this one tell ffmpeg need an output
output = "./result/"+name
p=subprocess.check_output(["ffmpeg", "-i", sourcedir+"/"+name+".mpg","-filter:v", "select='gt(scene,0.4)',metadata=print:file=output","-an","-f","null","-"])
this one give me no error but doesn't create the file
this is the original command that i use directly with ffmpeg:
ffmpeg -i input.flv -filter:v "select='gt(scene,0.4)',showinfo" -f null - 2> ffout
I just need the ouput of this command to be written to a file, anyone see how i could make it work?
is there a better way then subprocess ? or just another way ? will it be easier in C?
You can redirect the stderr output directly from Python without any need for shell=True which can lead to shell injection.
It's as simple as:
with open(output_path, 'w') as f:
subprocess.check_call(cmd, stderr=f)
Things are easier in your case if you use the shell argument of the subprocess command and it should behave the same. When using the shell command, you can pass in a string as the command rather then a list of args.
cmd = "ffmpeg -i {0} -filter:v \"select='gt(scene,0.4)',showinfo\" -f {1} - 2> ffout".format(inputName, outputFile)
p=subprocess.check_output(cmd, shell=True)
If you want to pass arguments, you can easily format your string

using find in subprocess.call() gives error while the command executes properly from command prompt

C:\Windows\System32> ffmpeg -i D:\devaraj\KPIX_test.ts -vf "blackframe" -an -f n
ull - 2>&1|find "Parsed" > D:\devaraj\info.txt
this works fine , writes the file info.txt
subprocess.call('ffmpeg' ,'-i', 'D:\devaraj\KPIX_test.ts' ,'-vf', '"blackframe"', 'D:\devaraj\KPIX_textfinal.mp3', '- 2>&1>','|','find', '"Parsed"', '>' ,'D:\devaraj\info.txt', 'shell=True')
gives an error buffer size must be integer
were as
subprocess.call('ffmpeg -i D:\devaraj\KPIX_test.ts -vf "blackframe" -an -f n
ull - 2>&1|find "Parsed" > D:\devaraj\info.txt', shell=True)
gives an error
'find' is not recognized as an internal or external command,
operable program or batch file.
any help would be appreciated from d bottom of heart
you should use native python methods to get filtered ffmpeg output:
ffmpeg -i D:\devaraj\KPIX_test.ts -vf "blackframe" -an -f null - 2>&1|find "Parsed"
To do this, you'd normally require check_output but this particular example is known to provide the required info but exit with a non-zero return code (using run from Python 3.5 would work, though)
So I'll use Popen instead. It becomes (as list, without all redirections and filters), then read all output from process standard output:
p = subprocess.Popen(["ffmpeg","-i",r"D:\devaraj\KPIX_test.ts",
"-vf","blackframe","-an","-f","null"],stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
output = p.stdout.read()
You don't need shell=True, and it merges error & output streams in the output variable.
Now output contains the output of ffmpeg command. Let's decode it (to get a string) and split the lines, check if the string is in the lines:
for line in output.decode().splitlines(): # python 2: output.splitlines()
if "Parsed" in line:
print(line.rstrip()) # or store it in a file, string, whatever
for a process outputting a lot more text, it would be better to iterate on p.stdout instead of reading the full contents (less memory hungry, allows real-time echo to the console)

Stream a Video output via ffmpeg Pipe into a Python Script for analisis. How to pipe into python?

I'm working on a script in conjunction with other libraries which requires an frame or image in an RGB24 format. For improved compatibility I have decided to allow for an external pipe to stream frames into this program. Changing the device or source every time with in the code can become tedious and using a parser to simply specify the source leads to syntax errors. Example:
ffmpeg -f dshow -i video="OEM Device" a.mpg
works exactly how you would think. However in an subprocess in python
pipe = sp.Popen('ffmpeg -f dshow -i video="OEM Device" a.mpg'.split(),...
Edit I have tried to manually split. 'video="OEM Device"' didn't work inside python either.
Leads to ' Invalid argument "OEM Separating OEM and Device as two different variables/arguments. I have tried the alternative name as well.
Which led me to believe
"
is the problem.
Which led me to piping the video stream into python via the terminal.
ffmpeg -i a.mpg -f image2pipe -vcode rawvideo -pix_fmt rgb24 - |python myscript.py
This is what I have in the Script.
import subprocess as sp
import numpy
import sys
import os
pipe = sp.Popen('ffmpeg -f rawvideo -pix_fmt rgb24 -an -vcodec rawvideo -i - -f image2pipe -pix_fmt rgb24 -an -vcodec rawvideo -'.split(), stdin=sys.stdin, stderr=sp.PIPE, stdout=sp.PIPE)
#Assumeing 720x576 resolution
raw_img = pipe.stdout.read(720*576*3)
image = numpy.fromstring(raw_img, dtype='uint8')
img_load = image.reshape(576, 720, 3)
I know the Above pipe is not needed and can probably be replaced by.(Which I have tried)
raw_img = sys.stdin.read(720*576*3)
Regardless of the two it ordinarily gives output, which results in
image.reshape(576,720,3)
to receive irregular dimensions and never the required 720x576 as is being specified. I have to admit this is the first time using pipes with python. As I understand stderr is Suppressed As I have specified image2pipe.
How can I let ffmpeg to either give python the required dimensions or give an subprocess the syntax ,which allows " in the given command without splitting the values or causing syntax errors?
Instead of writing a string and then .split()-ing it, just pass a proper array to start with:
.Popen(['ffmpeg', '-f', 'dshow', '-i', 'video="OEM Device"', 'a.mpg'], ...)
The command you are calling needs to see video="OEM Device" as a single element in its args array, so you need to pass it as a single element to the Popen args array.
Curtesy of #Grisha Levit: The Answer was to simply remove "
Instead of writing a string and then .split()-ing it, just pass a proper array to start with:
.Popen(['ffmpeg', '-f', 'dshow', '-i', 'video=OEM Device', 'a.mpg'], ...)
The command you are calling needs to see 'video=OEM Device' as a single element in its args array, so you need to pass it as a single element to the Popen args array.

Categories