Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for running tests on Windows #597

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion moviepy/Clip.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __init__(self):
def copy(self):
""" Shallow copy of the clip.

Returns a shwallow copy of the clip whose mask and audio will
Returns a shallow copy of the clip whose mask and audio will
be shallow copies of the clip's mask and audio if they exist.

This method is intensively used to produce new clips every time
Expand Down Expand Up @@ -288,6 +288,7 @@ def set_duration(self, t, change_end=True):
of the clip.
"""
self.duration = t

if change_end:
self.end = None if (t is None) else (self.start + t)
else:
Expand Down Expand Up @@ -489,3 +490,24 @@ def generator():
return tqdm(generator(), total=nframes)

return generator()

def close(self):
"""
Release any resources that are in use.

Implementation note for subclasses:
* Memory-based resources can be left to the garbage-collector.
* However, any open files should be closed, and subprocesses should be terminated.
* Be wary that shallow copies are frequently used. Closing a Clip may affect its copies.
* Therefore, should NOT be called by __del__().
"""
pass

# Support the Context Manager protocol, to ensure that resources are cleaned up.

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, traceback):
self.close()

43 changes: 28 additions & 15 deletions moviepy/audio/io/AudioFileClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,27 @@ class AudioFileClip(AudioClip):
buffersize
See Parameters.

Lifetime
--------

Note that this creates subprocesses and locks files. If you construct one of these instances, you must call
close() afterwards, or the subresources will not be cleaned up until the process ends.

If copies are made, and close() is called on one, it may cause methods on the other copies to fail.

However, coreaders must be closed separately.

Examples
----------

>>> snd = AudioFileClip("song.wav")
>>> snd.close()
>>> snd = AudioFileClip("song.mp3", fps = 44100, bitrate=3000)
>>> snd = AudioFileClip(mySoundArray,fps=44100) # from a numeric array
>>> second_reader = snd.coreader()
>>> second_reader.close()
>>> snd.close()
>>> with AudioFileClip(mySoundArray,fps=44100) as snd: # from a numeric array
>>> pass # Close is implicitly performed by context manager.

"""

Expand All @@ -59,28 +74,26 @@ def __init__(self, filename, buffersize=200000, nbytes=2, fps=44100):
AudioClip.__init__(self)

self.filename = filename
reader = FFMPEG_AudioReader(filename,fps=fps,nbytes=nbytes,
self.reader = FFMPEG_AudioReader(filename,fps=fps,nbytes=nbytes,
buffersize=buffersize)

self.reader = reader
self.fps = fps
self.duration = reader.duration
self.end = reader.duration
self.duration = self.reader.duration
self.end = self.reader.duration


self.make_frame = lambda t: reader.get_frame(t)
self.nchannels = reader.nchannels
self.make_frame = lambda t: self.reader.get_frame(t)
self.nchannels = self.reader.nchannels


def coreader(self):
""" Returns a copy of the AudioFileClip, i.e. a new entrance point
to the audio file. Use copy when you have different clips
watching the audio file at different times. """
return AudioFileClip(self.filename,self.buffersize)
return AudioFileClip(self.filename, self.buffersize)


def __del__(self):
""" Close/delete the internal reader. """
try:
del self.reader
except AttributeError:
pass
def close(self):
""" Close the internal reader. """
if self.reader:
self.reader.close_proc()
self.reader = None
26 changes: 21 additions & 5 deletions moviepy/audio/io/ffmpeg_audiowriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,27 @@ def write_frames(self,frames_array):


def close(self):
self.proc.stdin.close()
if self.proc.stderr is not None:
self.proc.stderr.close()
self.proc.wait()
del self.proc
if self.proc:
self.proc.stdin.close()
self.proc.stdin = None
if self.proc.stderr is not None:
self.proc.stderr.close()
self.proc.stdee = None
# If this causes deadlocks, consider terminating instead.
self.proc.wait()
self.proc = None

def __del__(self):
# If the garbage collector comes, make sure the subprocess is terminated.
self.close()

# Support the Context Manager protocol, to ensure that resources are cleaned up.

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, traceback):
self.close()



Expand Down
3 changes: 2 additions & 1 deletion moviepy/audio/io/readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def close_proc(self):
for std in [ self.proc.stdout,
self.proc.stderr]:
std.close()
del self.proc
self.proc = None

def get_frame(self, tt):

Expand Down Expand Up @@ -245,4 +245,5 @@ def buffer_around(self,framenumber):


def __del__(self):
# If the garbage collector comes, make sure the subprocess is terminated.
self.close_proc()
13 changes: 7 additions & 6 deletions moviepy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,9 @@ def try_cmd(cmd):
else:
success, err = try_cmd([FFMPEG_BINARY])
if not success:
raise IOError(err.message +
"The path specified for the ffmpeg binary might be wrong")


raise IOError(
str(err) +
" - The path specified for the ffmpeg binary might be wrong")

if IMAGEMAGICK_BINARY=='auto-detect':
if os.name == 'nt':
Expand All @@ -65,8 +64,10 @@ def try_cmd(cmd):
else:
success, err = try_cmd([IMAGEMAGICK_BINARY])
if not success:
raise IOError(err.message +
"The path specified for the ImageMagick binary might be wrong")
raise IOError(
"%s - The path specified for the ImageMagick binary might be wrong: %s" %
(err, IMAGEMAGICK_BINARY)
)



Expand Down
1 change: 1 addition & 0 deletions moviepy/video/VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ def write_videofile(self, filename, fps=None, codec=None,
>>> from moviepy.editor import VideoFileClip
>>> clip = VideoFileClip("myvideo.mp4").subclip(100,120)
>>> clip.write_videofile("my_new_video.mp4")
>>> clip.close()

"""
name, ext = os.path.splitext(os.path.basename(filename))
Expand Down
14 changes: 14 additions & 0 deletions moviepy/video/compositing/CompositeVideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ def __init__(self, clips, size=None, bg_color=None, use_bgclip=False,
if use_bgclip:
self.bg = clips[0]
self.clips = clips[1:]
self.created_bg = False
else:
self.clips = clips
self.bg = ColorClip(size, col=self.bg_color)
self.created_bg = True



Expand Down Expand Up @@ -117,6 +119,18 @@ def playing_clips(self, t=0):
actually playing at the given time `t`. """
return [c for c in self.clips if c.is_playing(t)]

def close(self):
if self.created_bg and self.bg:
# Only close the background clip if it was locally created.
# Otherwise, it remains the job of whoever created it.
self.bg.close()
self.bg = None
if hasattr(self, "audio") and self.audio:
self.audio.close()
self.audio = None





def clips_array(array, rows_widths=None, cols_widths=None,
Expand Down
28 changes: 19 additions & 9 deletions moviepy/video/io/VideoFileClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class VideoFileClip(VideoClip):
A video clip originating from a movie file. For instance: ::

>>> clip = VideoFileClip("myHolidays.mp4")
>>> clip2 = VideoFileClip("myMaskVideo.avi")
>>> clip.close()
>>> with VideoFileClip("myMaskVideo.avi") as clip2:
>>> pass # Implicit close called by contex manager.


Parameters
Expand Down Expand Up @@ -61,6 +63,14 @@ class VideoFileClip(VideoClip):


Read docs for Clip() and VideoClip() for other, more generic, attributes.

Lifetime
--------

Note that this creates subprocesses and locks files. If you construct one of these instances, you must call
close() afterwards, or the subresources will not be cleaned up until the process ends.

If copies are made, and close() is called on one, it may cause methods on the other copies to fail.

"""

Expand All @@ -74,7 +84,6 @@ def __init__(self, filename, has_mask=False,

# Make a reader
pix_fmt= "rgba" if has_mask else "rgb24"
self.reader = None # need this just in case FFMPEG has issues (__del__ complains)
self.reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt,
target_resolution=target_resolution,
resize_algo=resize_algorithm,
Expand Down Expand Up @@ -110,14 +119,15 @@ def __init__(self, filename, has_mask=False,
fps = audio_fps,
nbytes = audio_nbytes)

def __del__(self):
""" Close/delete the internal reader. """
try:
del self.reader
except AttributeError:
pass
def close(self):
""" Close the internal reader. """
if self.reader:
self.reader.close()
self.reader = None

try:
del self.audio
if self.audio:
self.audio.close()
self.audio = None
except AttributeError:
pass
13 changes: 3 additions & 10 deletions moviepy/video/io/ffmpeg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ def initialize(self, starttime=0):
self.proc = sp.Popen(cmd, **popen_params)





def skip_frames(self, n=1):
"""Reads and throws away n frames """
w, h = self.size
Expand Down Expand Up @@ -155,7 +152,7 @@ def get_frame(self, t):

Note for coders: getting an arbitrary frame in the video with
ffmpeg can be painfully slow if some decoding has to be done.
This function tries to avoid fectching arbitrary frames
This function tries to avoid fetching arbitrary frames
whenever possible, by moving between adjacent frames.
"""

Expand Down Expand Up @@ -186,15 +183,11 @@ def close(self):
self.proc.stdout.close()
self.proc.stderr.close()
self.proc.wait()
del self.proc

def __del__(self):
self.close()
if hasattr(self,'lastread'):
self.proc = None
if hasattr(self, 'lastread'):
del self.lastread



def ffmpeg_read_image(filename, with_mask=True):
""" Read an image file (PNG, BMP, JPEG...).

Expand Down
45 changes: 26 additions & 19 deletions moviepy/video/io/ffmpeg_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def __init__(self, filename, size, fps, codec="libx264", audiofile=None,
# This was added so that no extra unwanted window opens on windows
# when the child process is created
if os.name == "nt":
popen_params["creationflags"] = 0x08000000
popen_params["creationflags"] = 0x08000000 # CREATE_NO_WINDOW

self.proc = sp.Popen(cmd, **popen_params)

Expand Down Expand Up @@ -178,12 +178,21 @@ def write_frame(self, img_array):
raise IOError(error)

def close(self):
self.proc.stdin.close()
if self.proc.stderr is not None:
self.proc.stderr.close()
self.proc.wait()
if self.proc:
self.proc.stdin.close()
if self.proc.stderr is not None:
self.proc.stderr.close()
self.proc.wait()

del self.proc
self.proc = None

# Support the Context Manager protocol, to ensure that resources are cleaned up.

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, traceback):
self.close()

def ffmpeg_write_video(clip, filename, fps, codec="libx264", bitrate=None,
preset="medium", withmask=False, write_logfile=False,
Expand All @@ -198,24 +207,22 @@ def ffmpeg_write_video(clip, filename, fps, codec="libx264", bitrate=None,
logfile = None

verbose_print(verbose, "[MoviePy] Writing video %s\n"%filename)
writer = FFMPEG_VideoWriter(filename, clip.size, fps, codec = codec,
with FFMPEG_VideoWriter(filename, clip.size, fps, codec = codec,
preset=preset, bitrate=bitrate, logfile=logfile,
audiofile=audiofile, threads=threads,
ffmpeg_params=ffmpeg_params)

nframes = int(clip.duration*fps)
ffmpeg_params=ffmpeg_params) as writer:

for t,frame in clip.iter_frames(progress_bar=progress_bar, with_times=True,
fps=fps, dtype="uint8"):
if withmask:
mask = (255*clip.mask.get_frame(t))
if mask.dtype != "uint8":
mask = mask.astype("uint8")
frame = np.dstack([frame,mask])
nframes = int(clip.duration*fps)

writer.write_frame(frame)
for t,frame in clip.iter_frames(progress_bar=progress_bar, with_times=True,
fps=fps, dtype="uint8"):
if withmask:
mask = (255*clip.mask.get_frame(t))
if mask.dtype != "uint8":
mask = mask.astype("uint8")
frame = np.dstack([frame,mask])

writer.close()
writer.write_frame(frame)

if write_logfile:
logfile.close()
Expand Down
Loading