The original Problem - MCVE
The following script should use chrome headless, to print to pdf (I am running windows 10, and python 3.6):
import subprocess
from tempfile import NamedTemporaryFile
output = NamedTemporaryFile()
CHROME_PATH=r'"C:\Program Files (x86)\Google\Chrome\Application\chrome"'
chrome_args=[CHROME_PATH,
'--headless',
r'--print-to-pdf="{}"'.format(output.name),
'--disable-gpu',
'https://www.google.com/',]
subprocess.call(chrome_args,shell=True)
However the generated file, is just empty.
Attempt at debugging
To try and figure out what is going, on I adapted the script to the following:
import subprocess
CHROME_PATH=r'"C:\Program Files (x86)\Google\Chrome\Application\chrome"'
chrome_args=[CHROME_PATH,
'--headless',
r'--print-to-pdf="c:\Users\timmc\Documents\output.pdf"',
'--disable-gpu',
'https://www.google.com/',]
print(r" ".join(chrome_args)) #For debuging
subprocess.call(chrome_args,shell=True)
In this case, there is just no file generated at the expected location. The result of the print is:
"C:\Program Files (x86)\Google\Chrome\Application\chrome" --headless --print-to-pdf="c:\Users\timmc\Documents\output.pdf" --disable-gpu https://www.google.com/
if I run the following (creating a raw string literal), everything works as expected and the file is produced.
subprocess.call(r'"C:\Program Files (x86)\Google\Chrome\Application\chrome" --headless --print-to-pdf="c:\Users\timmc\Documents\output.pdf" --disable-gpu https://www.google.com/', shell=True)
Having searched around on stack-overflow, and tried a few things, I still can’t get the original script to work. Any ideas?
Part of the problem is that I can't seem to get any meaningful debug from the subprocess call. Any help with that would also be much appreciated.
I'll try to answer instead of commenting again and again, but obviously I cannot test this.
The issue is mainly the forcing of the double quotes & shell=True. Leaving the quoting to subprocess (also in CHROME_PATH) and splitting arguments properly usually work. I've solved a lot of questions here with this technique.
Since your comments state that it does not, and that you found a workaround, let me suggest an improvement of this workaround: injecting the output filename in the command line that works:
subprocess.call(r'"C:\Program Files (x86)\Google\Chrome\Application\chrome" --headless --print-to-pdf="{}" --disable-gpu https://www.google.com/'.format(output.name), shell=True)
not satisfactory to me but it has a good chance to work.
It turns out that the reason the subprocess wasn't running properly, is that when python creates a NamedTemporaryFile in windows, it does so with a FILE_SHARE_DELETE tag which prevents any other process accessing it unless it also has this tag. There is more discussion of this here.
Fortunately, Django comes with its own NamedTemporaryFile which was made to partially address this problem, and does so well enough for these purposes.
Related
I try to automatise an image stitching process from python using the software PTGui.
I can execute the following command which works perfectly in the windows command line :
C:\Users\mw4168> "C:\Program Files\PTGui\ptgui.exe" -batch "C:\Users\mw4168\OneDrive\Desktop\PTGui Tests\3 rows\Panorama.pts"
command screenshot here
However, when I try to execute this command using os.system or subprocess.run in Python:
import os
os.system("C:\Program Files\PTGui\ptgui.exe" + "-batch" +"C:\Users\mw4168\OneDrive\Desktop\PTGui Tests\3 rows\panorama.pts")
I get this error :
'C:\Program' is not recognized as an internal or external command, operable program or batch file.
error screenshot here
It seems that there is an issue with the spaces within the string... Any idea on how to fix this?
Thanks a lot in advance,
Paul
Like the os.system documentation already tells you, a better solution altogether is to use subprocess instead.
subprocess.run([
r"C:\Program Files\PTGui\ptgui.exe",
"-batch",
r"C:\Users\mw4168\OneDrive\Desktop\PTGui Tests\3 rows\panorama.pts"])
The string you created also lacked spaces between the indvidual arguments; but letting Python pass the arguments to the OS instead also gives you more control over quoting etc.
The issue is that you're probably (as you have posted no code) not passing the C:\Program Files\PTGui\ptgui.exe as a string to the terminal but just a plain command
Use
import os
os.system('\"C:\Program Files\PTGui\ptgui.exe\" -batch')
Even though there are a handful of threads on this issue, no solutions have helped me, here is the problematic lines of code:
AudioSegment.converter = r'C:/users/user_/appdata/local/packages/pythonsoftwarefoundation.python.3.8_qbz5n2kfra8p0/localcache/local-packages/python38/site-packages/ffmpeg.exe'
AudioSegment.ffprobe = r'C:/users/user_/appdata/local/packages/pythonsoftwarefoundation.python.3.8_qbz5n2kfra8p0/localcache/local-packages/python38/site-packages/ffprobe.exe'
final_voice = AudioSegment.from_mp3(file_path) + AudioSegment.silent(duration=silence_duration)
I have tried different methods to solve this issue, such as adding the paths to ffmpeg.exe and ffprobe.exe but nothing changed after that, other solutions do not make much sense as I am not using the modules they have had issues with and I did not do things they have done.
If you have any ideas please share as I have not found how to do the things AudioSegment does with other modules (by the way this issue has come up in every line of code containing AudioSegment.from_mp3(file_path))
Thanks
The solution is quite simple, you have to add ffmpeg.exe, ffprobe.exe and ffplay.exe into your script directory. Download these exe files from the FFMPEG download page and take them from the bin folder
Adding the ffmpeg files was not an option for me so I dug a little deeper. Short answer:
Change the else clause of get_prober_name() in your local ...\site-packages\pydub\utils.py (line 199 in my current version) to return the absolute path of your ffprobe.exe. After that the following code worked for me:
from pydub import AudioSegment
AudioSegment.converter = 'D:/Stuff/Software/ffmpeg/bin/ffmpeg.exe'
AudioSegment.ffprobe = 'D:/Stuff/Software/ffmpeg/bin/ffprobe.exe' # this does nothing!
mp3_fol = "D:/mp3/"
mp3_file = AudioSegment.from_mp3(mp3_fol + "my.mp3")
I'm using Windows with a unzipped version of ffmpeg (so no installation, Path entry or similar). While the converter method seemed to acutally set a value, the ffprobe method didn't do anything.
The subprocess the script is calling simply calls 'ffprobe' as a program. Which of course will not work if this is not a registered program. So the easiest (and hopefully safest) way to circumvent this behaviour is to set the default prober name to the correct full path (as done above).
(Background: On an NTFS partition, files and/or folders can be set to "compressed", like it's a file attribute. They'll show up in blue in Windows Explorer, and will take up less disk space than they normally would. They can be accessed by any program normally, compression/decompression is handled transparently by the OS - this is not a .zip file. In Windows, setting a file to compressed can be done from a command line with the "Compact" command.)
Let's say I've created a file called "testfile.txt", put some data in it, and closed it. Now, I want to set it to be NTFS compressed. Yes, I could shell out and run Compact, but is there a way to do it directly in Python code instead?
In the end, I ended up cheating a bit and simply shelling out to the command line Compact utility. Here is the function I ended up writing. Errors are ignored, and it returns the output text from the Compact command, if any.
def ntfscompress(filename):
import subprocess
_compactcommand = 'Compact.exe /C /I /A "{}"'.format(filename)
try:
_result = subprocess.run(_compactcommand, timeout=86400,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,text=True)
return(_result.stdout)
except:
return('')
I am trying to make a python script that will open a directory, apply a perl script to every file in that directory and get its out put in either multiple text files or just one.
I currently have:
import shlex, subprocess
arg_str = "perl tilt.pl *.pdb > final.txt"
arg = shlex.split(arg_str)
import os
framespdb = os.listdir("prac_frames")
for frames in framespdb:
subprocess.Popen(arg, stdout=True)
I keep getting *.pdb not found. I am very new to all of this so any help trying to complete this script would help.
*.pdb not found means exactly that - there won't be a *.pdb in whatever directory you're running the script... and as I read the code - I don't see anything to imply it's within 'frames' when it runs the perl script.
you probably need os.chdir(path) before the Popen.
How do I "cd" in Python?
...using a python script to run somewhat dubious syscalls to perl may offend some people but everyone's done it.. aside from that I'd point out:
always specify full paths (this becomes a problem if you will later say, want to run your job automatically from cron or an environment that doesn't have your PATH).
i.e. 'which perl' - put that full path in.
./.pdb would be better but not as good as the fullpath/.pdb (which you could use instead of the os.chdir option).
subprocess.Popen(arg, stdout=True)
does not expand filename wildcards. To handle your *.pdb, use shell=True.
I spend a few hours writing a little script.
Basically what it does is create a new text file and fills it up with whatever.
I zip the text file --using zipfile-- and here's where my problem lies.
I want to run the Windows system command:
copy /b "imgFile.jpg" + "zipFile.zip" newImage.jpg
To merge the image "imgFile.jpg" and the zip "zipFile.zip".
So:
os.system("copy /b \"imgFile.jpg\" + \"zipFile.zip\" newImage.jpg")
When I run my script, it all seems to go fine.
But when it's done and I try to extract the 'newImage.jpg' file, it gives me:
The archive is either in unknown format or damaged
This ONLY happens when I run the system command within the script.
It works fine when I use the shell. It even works if I use a separate script.
I've double checked my zip file. Everything is in good shape.
Is there something I'm doing wrong? Something I'm not seeing?
Have you tried using shutil?
import shutil
shutil.copy(src, dst)
There may be a problem with the way Python is passing the arguments to the shell command. Try using subprocess.call. This method takes arguments as an array and passes them that way to the command:
import subprocess
subprocess.call(["copy", "/b", '"imgFile.jpg" + "zipFile.zip"', "newImage.jpg"])