Building an executable with PyInstaller
Hi,
I've been using the MoviePy library (which is very awesome) to generate some teaching videos. Now, to make my video script easier to use for my colleagues, I want to turn the python script into a single executable using PyInstaller.
PyInstaller runs fine, and does it's thing - creates the executable, but when I try to run it, i get the following error:
C:\Users\domcib01\Documents\Lab Videos\Python\LAB_NAME\OS\LabTemplates\Python\LAB1>dist\lab1script\lab1script.exe
Traceback (most recent call last):
File "<string>", line 7, in <module>
File "C:\Python27\Lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 389, in load_module exec(bytecode, module.__dict__)
File "moviepy\editor.py", line 34, in <module>
File "C:\Python27\Lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 389, in load_module exec(bytecode, module.__dict__)
File "moviepy\video\fx\all\__init__.py", line 16, in <module>
WindowsError: [Error 3] The system cannot find the path specified: 'C:\\Users\\domcib01\\DOCUME~1\\LABVID~1\\Python\\LAB_NAME\\OS\\LABTEM~1\\Python\\LAB1\\dist\\LAB1SC~1\\moviepy\\video\\fx/*.*'
lab1script returned -1
I've tried creating other exec files too, but when I add a from moviepy.editor import * or any other import from MoviePy I keep getting the same error.
I'm running Python 2.7 on a 64-bit Windows 7 PC.
Maybe it is due to the fact that ffmpeg is not bundled and is downloaded on trying to execute the moviepy imported code for the first time. Hence, because of that, it is not explicitly defined as a dependency and pyinstaller is unable to bundle that together leading to the following error. I am getting the same error.
Thanks for confirming this issue. So do you think this is an ImageIO problem, rather than a MoviePy problem?
Last week I got a chance to go dirty with moviepy module source in <PYTHON_DIR>/lib/site-packages to find root of the problem.
I found it at this place.
for _name in __all__:
exec("from ..%s import %s"%(_name,_name))
Moviepy uses a neat "exec" trick to dynamically import all the modules at fx level. Pyinstaller fails to recognise that and hence, it throws up the following error. It is the same case with audio/fx/all
Solution
It is more of a hack. Replace the for loop and exec statement with actual import statements. Thus , it should look like this
from moviepy.video.fx.accel_decel import accel_decel
from moviepy.video.fx.blackwhite import blackwhite
Do the same for audio/fx/all/__init__.py
I suggest copying moviepy folder to your local project and modify it there so that original module remains unchanged for rest of the projects.
Build your application once to generate pyc of your changes in moviepy folder too.
Then, build the executable with pyinstaller in the usual way and try to run it. It will throw the same error as above. Then, you should go to the respective folder (C:\\Users\\domcib01\\DOCUME~1\\LABVID~1\\Python\\LAB_NAME\\OS\\LABTEM~1\\Python\\LAB1\\dist\\LAB1SC~1 in this case), and copy the modified moviepy folder there.
Try running the executable once again and it will work.
I don't have much time to look into this but someone seems to have fixed my very hackish code for fx.all in this in pull request: #275 because they had apparently the same problem but with pyfreeze. Can you guys try Pyinstaller on his version and report back ?
@Zulko I can confirm that it does not work. Pyinstaller, still, is not able to bundle the changes , probably due to exec still being there.
thank you very much @pratikone for your solution, it worked for me, even that i dont understand it fully. but I cant use --onefile?? because moviepy look for fx at temp. thank you again
Just pinging here, any updates? I have the same issue. It seems like the relative app directory. If you open the app from the package contents, it runs, but double clicking on the icon from /Applications fails.
Is the suggested change the moviepy source still best practice?
Okay, I've tried the suggested fix but no luck. Let me document my work here.
The problem
My app successful runs in the following conditions.
-
When running from source.
-
After pyinstaller build, in the dist folder
MacBook-Pro:dist ben$ pwd
/Users/ben/Documents/DeepMeerkat/Installer/Mac/dist
MacBook-Pro:dist ben$ ./DeepMeerkat.app/Contents/MacOS/main --help
['./DeepMeerkat.app/Contents/MacOS/main', '--help']
usage: main [-h] [--input INPUT] [--draw_size DRAW_SIZE] [--size SIZE]
[--tensorflow_threshold TENSORFLOW_THRESHOLD]
[--moglearning MOGLEARNING] [--mogvariance MOGVARIANCE] [--crop]
[--draw_box] [--show] [--threaded] [--tensorflow] [--write_text]
[--training] [--output_video] [--resize]
[--path_to_model PATH_TO_MODEL] [--output OUTPUT]
- After copying to /Applications folder, it runs as alias
MacBook-Pro:~ ben$ open -a "DeepMeerkat"
but, it cannot open on double click of icon. No error message. No system log on console, nothing. The error only occurred after moviepy. Removing moviepy fixes the error.
Attempts
- Found the source of moviepy
import moviepy
moviepy.__path__
['/Library/Python/2.7/site-packages/moviepy']
went to that dir, deleted the .pyc and changed the init to do direct imports as noted above.
MacBook-Pro:all ben$ cat __init__.py
"""
Loads all the fx !
Usage:
import moviepy.video.fx.all as vfx
clip = vfx.resize(some_clip, width=400)
clip = vfx.mirror_x(some_clip)
"""
import os
_directory = os.path.dirname(
os.path.dirname(
os.path.realpath(__file__)))
_files = os.listdir(_directory)
_fx_list = [_f for _f in _files if ( _f.endswith('.py') and
not _f.startswith('_'))]
__all__ = [_c[:-3] for _c in _fx_list]
#for _name in __all__:
#exec("from ..%s import %s"%(_name,_name))
from moviepy.video.fx.accel_decel import accel_decel
from moviepy.video.fx.blackwhite import blackwhite
from moviepy.video.fx.blink import blink
from moviepy.video.fx.colorx import colorx
from moviepy.video.fx.crop import crop
from moviepy.video.fx.even_size import even_size
from moviepy.video.fx.fadein import fadein
from moviepy.video.fx.fadeout import fadeout
from moviepy.video.fx.freeze import freeze
from moviepy.video.fx.freeze_region import freeze_region
from moviepy.video.fx.gamma_corr import gamma_corr
from moviepy.video.fx.headblur import headblur
from moviepy.video.fx.invert_colors import invert_colors
from moviepy.video.fx.loop import loop
from moviepy.video.fx.lum_contrast import lum_contrast
from moviepy.video.fx.make_loopable import make_loopable
from moviepy.video.fx.margin import margin
from moviepy.video.fx.mask_and import mask_and
from moviepy.video.fx.mask_color import mask_color
from moviepy.video.fx.mask_or import mask_or
from moviepy.video.fx.mirror_x import mirror_x
from moviepy.video.fx.mirror_y import mirror_y
from moviepy.video.fx.painting import painting
from moviepy.video.fx.resize import resize
from moviepy.video.fx.rotate import rotate
from moviepy.video.fx.scroll import scroll
from moviepy.video.fx.speedx import speedx
from moviepy.video.fx.supersample import supersample
from moviepy.video.fx.time_mirror import time_mirror
from moviepy.video.fx.time_symmetrize import time_symmetrize
print "DONE"
I did this for both the /video/fx/all and the audio/fx/all modules. I ran my app a couple times, but i did notice that the .pyc are not recreated. Could this be an issue? I deleted them initially.
- Rebuilt my app using pyinstaller
pyinstaller --windowed --onedir DeepMeerkat.spec
- Copy the .app to /Applications folder.
Double click fails, but from terminal
open ./DeepMeerkat.app
works fine.
I'm struggling with debugging avenues. Suggestions?
Add on here. I added
try:
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
except Exception as e:
with open('/Users/ben/Desktop/error.txt',mode="w+") as f:
f.write(str(e))
and rebuilt with pyinstaller. I got
Need ffmpeg exe. You can download it by calling:
imageio.plugins.ffmpeg.download()
but I do have ffmpeg in my path!
MacBook-Pro:dist ben$ ffmpeg
ffmpeg version 3.3.4 Copyright (c) 2000-2017 the FFmpeg developers
built with Apple LLVM version 8.1.0 (clang-802.0.42)
configuration: --prefix=/usr/local/Cellar/ffmpeg/3.3.4 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-libmp3lame --enable-libx264 --enable-libxvid --enable-opencl --enable-videotoolbox --disable-lzma --enable-vda
libavutil 55. 58.100 / 55. 58.100
libavcodec 57. 89.100 / 57. 89.100
libavformat 57. 71.100 / 57. 71.100
libavdevice 57. 6.100 / 57. 6.100
libavfilter 6. 82.100 / 6. 82.100
libavresample 3. 5. 0 / 3. 5. 0
libswscale 4. 6.100 / 4. 6.100
libswresample 2. 7.100 / 2. 7.100
libpostproc 54. 5.100 / 54. 5.100
Hyper fast Audio and Video encoder
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
Not only that, what is more curious is that my .App has been using ffmpeg through opencv video capture for years. If not, I would have never been able to read video. Its bundled in, and never before has it had problems finding it. I'm now looking to see if moviepy has changed the env variables in some way.
Edit #1
Perhaps I was a little hasty in saying that I had ffmpeg from opencv. What opencv has is the ffmpeg .dll, so it is different. I'm now going down the route of editing pyinstaller to include the ffmpeg binary in the build and then editing the moviepy defaults config.
I hope I am commenting in the right way but at trying to create an exe file with pyinstaller and after following all the steps presented in this issue and also modifying the moviepy library and doing my own local copy changing the moviepy/video/fx/all/init.py and moviepy/audio/fx/all/init.py now I have the error below:
Exception in Tkinter callback Traceback (most recent call last): File "tkinter_init_.py", line 1884, in call File "youtube.py", line 99, in DescargarVideo File "youtube.py", line 121, in VideoAudioConverter File "moviepy\video\io\VideoFileClip.py", line 88, in init self.reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt, File "moviepy\video\io\ffmpeg_reader.py", line 35, in init infos = ffmpeg_parse_infos(filename, print_infos, check_duration, File "moviepy\video\io\ffmpeg_reader.py", line 257, in ffmpeg_parse_infos proc = sp.Popen(cmd, **popen_params) File "subprocess.py", line 947, in init File "subprocess.py", line 1416, in _execute_child FileNotFoundError: [WinError 2] The system cannot find the file specified
Someone has a suggestion about this? or I´m doing something wrong? Thank you in advance for the help
Actually you don't strictly have to edit the library source before bundling up. Instead of doing that, just import all moviepy modules in one of your scripts like so (source):
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.video.VideoClip import ImageClip
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
from moviepy.audio.io.AudioFileClip import AudioFileClip
from moviepy.audio.AudioClip import AudioClip
from moviepy.editor import concatenate_videoclips,concatenate_audioclips,TextClip,CompositeVideoClip
from moviepy.video.fx.accel_decel import accel_decel
from moviepy.video.fx.blackwhite import blackwhite
from moviepy.video.fx.blink import blink
from moviepy.video.fx.colorx import colorx
from moviepy.video.fx.crop import crop
from moviepy.video.fx.even_size import even_size
from moviepy.video.fx.fadein import fadein
from moviepy.video.fx.fadeout import fadeout
from moviepy.video.fx.freeze import freeze
from moviepy.video.fx.freeze_region import freeze_region
from moviepy.video.fx.gamma_corr import gamma_corr
from moviepy.video.fx.headblur import headblur
from moviepy.video.fx.invert_colors import invert_colors
from moviepy.video.fx.loop import loop
from moviepy.video.fx.lum_contrast import lum_contrast
from moviepy.video.fx.make_loopable import make_loopable
from moviepy.video.fx.margin import margin
from moviepy.video.fx.mask_and import mask_and
from moviepy.video.fx.mask_color import mask_color
from moviepy.video.fx.mask_or import mask_or
from moviepy.video.fx.mirror_x import mirror_x
from moviepy.video.fx.mirror_y import mirror_y
from moviepy.video.fx.painting import painting
from moviepy.video.fx.resize import resize
from moviepy.video.fx.rotate import rotate
from moviepy.video.fx.scroll import scroll
from moviepy.video.fx.speedx import speedx
from moviepy.video.fx.supersample import supersample
from moviepy.video.fx.time_mirror import time_mirror
from moviepy.video.fx.time_symmetrize import time_symmetrize
from moviepy.audio.fx.audio_fadein import audio_fadein
from moviepy.audio.fx.audio_fadeout import audio_fadeout
from moviepy.audio.fx.audio_left_right import audio_left_right
from moviepy.audio.fx.audio_loop import audio_loop
from moviepy.audio.fx.audio_normalize import audio_normalize
from moviepy.audio.fx.volumex import volumex
even though this is still not desirable, it is a working hack for the time being.
from moviepy.editor import
has
for method in [
"afx.audio_fadein",
"afx.audio_fadeout",
[...]
"vfx.speedx",
]:
exec("VideoClip.%s = %s" % (method.split(".")[1], method))
So importing the editor module has the same problem. It also means you can't chain commands (e.g. Videoclip().resize())
Closing this issue as it's old and references an older Python version we no longer support.
Do report back – following our issue template – if the problem persists when you try with the latest master (not the last PyPI release). Ideally, you'd open a new issue for that.
I had the same problem with python 3.10.5 on windows 10 with moviepy 1.0.3 and pyinstaller 5.7.0. The @Zenahr answer fixed my problem.