How do I copy a PIL picture to clipboard - python

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()})

Related

Is there a way for pdftotext (linux poppler-utils) to take a binary instead of a pdf file?

pdftotext looks like it only takes the pdf file name or the path to it. The docs aren't extremely helpful (https://www.cyberciti.biz/faq/converter-pdf-files-to-text-format-command/) (https://linux.die.net/man/1/pdftotext)
Is there a way to send the binary contents directly into this?
Let's say i'm grabbing a url that directly links to a PDF. I grab the response of that url using python requests,
response = requests.get(somePdfUrl)
I grab the binary,
pdfBinary = response.content
And I want to be able to send it into this function and run it using subprocess but normally it would be like this:
def textExtract(pdfBinary):
text = subprocess.run(['pdftotext', '/path/to/file.pdf'],
stdout=PIPE, stderr=PIPE)
This might be impossible and limited to the package but is there way to somehow to insert the pdfBinary into this method? I don't want to have to save the pdf file everytime and then insert it into the subprocess.
Yes, It's possible:
from subprocess import Popen, PIPE
command = ['pdftotext', '-layout', '-', '-']
p = Popen(command, stdout=PIPE, stdin=PIPE)
r = requests.get(url)
output = p.communicate(input=r.content)[0].decode()
I solved this by using a modified dockerized version of this utility, say you have a
Dockerfile:
FROM minidocks/poppler
CMD cp /dev/stdin myfile.pdf && pdftohtml -noframes -stdout myfile.pdf
Build the docker:
$ docker build . -t pdftohtml
Then you can do something like
$ cat some-pdf.pdf | docker run -i pdftohtml
This can be called from your python script, without the cat of course, you'd feed your binary data to the process.

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

Manipulating image output from shell in python without storing external file

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

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.

Python: Write stdout to log file; output is hexadecimal not ascii

I'm working on a script to call an executable for i/o files. I'm using subprocess and trying to shell out the exe and the stdout to a log file. Problem is I would like to output a simple ascii file and I'm getting a hexadecimal file. Just really learning to program python (or any language for that matter) so, I'm assuming there some type of formatting I can do but I just don't get it. I've done a fair bit of searching on this site and others but I haven't anything like what I'm using subprocess for. The "outRadcorr" is what I need help on the most...Any ideas? More code on request.
Import system modules
import os, sys, string, traceback, time, datetime
import params
from subprocess import Popen, PIPE, STDOUT
...some code here.....
Write stdout to log file
rad_log_file = open(dsFolder + '\\radcorr.log', 'w')
# loop through the files in raw file list to run radiometric correction
for rawfiles in rawFolderList:
# Define the file base
rawBase = rawfiles.split(".")[0]
print ('\nProcessing file: %s \n')%( rawBase )
# define variables from raw file to process radcorr
radFile = rawfiles
pixFile = ('%s.pix')%( rawBase )
attFile = ('%s.att')%( rawBase )
# create windose bat file function
def rad_bat_writer( radcorr_bat ):
with open(dsFolder + '\\radcorr.bat', 'a') as rad_bat_file:
rad_bat_file.write(radcorr_bat + '\n')
# grab radcor input/output files and run radcorr.exe
radcorr = ('C:\\itres\\bin\\radcorr.exe -f 1 -j 100 -g 50 -s -1 -n -1 %s %s %s -i '+ rawFolder + '\%s,rb -o ' + radFolder + '\%s -a ' \
+ radFolder + '\%s -c C:\\itres\\rad_cal_files\\%s -I 0 -v 0 -r Y -R Y -^^ 2') %( sum,scatter,shift,radFile,pixFile,attFile,rad_prefix )
# print out radcorr command
print radcorr
# Execute radcorr and write stdout
outRadcorr = Popen("{};".format(radcorr), stdout=PIPE, stderr=STDOUT)
for line in outRadcorr.stdout:
sys.stdout.write(line)
rad_log_file.write(line)
# write output to log
rad_bat_writer( radcorr )
# Close out exe and log files
outRadcorr.wait()
rad_log_file.close()
I was using UltraEdit to view the file, which you can view hex files in. I was not looking in hex mode. I might be confusing the terminology here. File looks normal in NotePad/NotePad++/WordPad etc. As I ran the script in debug mode, I could hit the loop file by file. First two files (1GB/file), the output log looked fine. Once the radcorr.log file was over 10kb, I change from a normal ascii output to this binary file viewed that looked hex. I can't post images yet, but just google ultraedit hexadecimal.
Still not sure why it moved to this format. Finally, output size was 45kb. I change to view/edit hex mode in UltraEdit and it looks fine. Just wanted to get it out there to see if others had any ideas why when I specified the log to be 'w' and not 'wb', for instance.
I do appreciate all you help. #J.F. Sebastian I'll have to test the code you posted, probably help fix potential bugs down the road.

Categories