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

add a decorator to quit pygame #766

Closed
wants to merge 13 commits into from
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ install:
- echo "No install action required. Implicitly performed by the testing."
- pip install flake8

# Taken from https://github.com/takluyver/pygame/blob/master/.travis.yml to
# get pygame to work
env:
global:
- SDL_VIDEODRIVER=dummy
- SDL_AUDIODRIVER=disk

before_script:
# stop the build if there are Python syntax errors or undefined names
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics --exit-zero
Expand Down
30 changes: 18 additions & 12 deletions moviepy/audio/io/preview.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import time
import numpy as np

from moviepy.decorators import requires_duration
from moviepy.decorators import requires_duration, pygame_quit

import pygame as pg

Expand All @@ -10,49 +10,50 @@


@requires_duration
@pygame_quit
def preview(clip, fps=22050, buffersize=4000, nbytes=2, audioFlag=None,
videoFlag=None):
"""
Plays the sound clip with pygame.

Parameters
-----------

fps
Frame rate of the sound. 44100 gives top quality, but may cause
problems if your computer is not fast enough and your clip is
complicated. If the sound jumps during the preview, lower it
(11025 is still fine, 5000 is tolerable).

buffersize
The sound is not generated all at once, but rather made by bunches
of frames (chunks). ``buffersize`` is the size of such a chunk.
Try varying it if you meet audio problems (but you shouldn't
have to).

nbytes:
Number of bytes to encode the sound: 1 for 8bit sound, 2 for
16bit, 4 for 32bit sound. 2 bytes is fine.

audioFlag, videoFlag:
Instances of class threading events that are used to synchronize
video and audio during ``VideoClip.preview()``.

"""

pg.mixer.quit()

pg.mixer.init(fps, -8 * nbytes, clip.nchannels, 1024)
totalsize = int(fps*clip.duration)
pospos = np.array(list(range(0, totalsize, buffersize))+[totalsize])
tt = (1.0/fps)*np.arange(pospos[0], pospos[1])
sndarray = clip.to_soundarray(tt, nbytes=nbytes, quantize=True)
chunk = pg.sndarray.make_sound(sndarray)

if (audioFlag is not None) and (videoFlag is not None):
audioFlag.set()
videoFlag.wait()

channel = chunk.play()
for i in range(1, len(pospos)-1):
tt = (1.0/fps)*np.arange(pospos[i], pospos[i+1])
Expand All @@ -62,7 +63,12 @@ def preview(clip, fps=22050, buffersize=4000, nbytes=2, audioFlag=None,
time.sleep(0.003)
if videoFlag is not None:
if not videoFlag.is_set():
channel.stop()
try:
channel.stop()
except pg.error:
# pg may be quitted and mixer not initilized to perform
# this.
pass
del channel
return
channel.queue(chunk)
38 changes: 26 additions & 12 deletions moviepy/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import decorator
from moviepy.tools import cvsecs

import pygame as pg


@decorator.decorator
Expand All @@ -25,7 +25,7 @@ def convert_masks_to_RGB(f, clip, *a, **k):
def apply_to_mask(f, clip, *a, **k):
""" This decorator will apply the same function f to the mask of
the clip created with f """

newclip = f(clip, *a, **k)
if hasattr(newclip, 'mask') and (newclip.mask is not None):
newclip.mask = f(newclip.mask, *a, **k)
Expand All @@ -37,7 +37,7 @@ def apply_to_mask(f, clip, *a, **k):
def apply_to_audio(f, clip, *a, **k):
""" This decorator will apply the function f to the audio of
the clip created with f """

newclip = f(clip, *a, **k)
if hasattr(newclip, 'audio') and (newclip.audio is not None):
newclip.audio = f(newclip.audio, *a, **k)
Expand All @@ -47,7 +47,7 @@ def apply_to_audio(f, clip, *a, **k):
@decorator.decorator
def requires_duration(f, clip, *a, **k):
""" Raise an error if the clip has no duration."""

if clip.duration is None:
raise ValueError("Attribute 'duration' not set")
else:
Expand All @@ -58,12 +58,12 @@ def requires_duration(f, clip, *a, **k):
@decorator.decorator
def audio_video_fx(f, clip, *a, **k):
""" Use an audio function on a video/audio clip

This decorator tells that the function f (audioclip -> audioclip)
can be also used on a video clip, at which case it returns a
videoclip with unmodified video and modified audio.
"""

if hasattr(clip, "audio"):
newclip = clip.copy()
if clip.audio is not None:
Expand All @@ -74,13 +74,13 @@ def audio_video_fx(f, clip, *a, **k):

def preprocess_args(fun,varnames):
""" Applies fun to variables in varnames before launching the function """

def wrapper(f, *a, **kw):
if hasattr(f, "func_code"):
func_code = f.func_code # Python 2
else:
func_code = f.__code__ # Python 3

names = func_code.co_varnames
new_a = [fun(arg) if (name in varnames) else arg
for (arg, name) in zip(a, names)]
Expand All @@ -98,7 +98,7 @@ def convert_to_seconds(varnames):

@decorator.decorator
def add_mask_if_none(f, clip, *a, **k):
""" Add a mask to the clip if there is none. """
""" Add a mask to the clip if there is none. """
if clip.mask is None:
clip = clip.add_mask()
return f(clip, *a, **k)
Expand All @@ -108,7 +108,7 @@ def add_mask_if_none(f, clip, *a, **k):
@decorator.decorator
def use_clip_fps_by_default(f, clip, *a, **k):
""" Will use clip.fps if no fps=... is provided in **k """

def fun(fps):
if fps is not None:
return fps
Expand All @@ -126,12 +126,26 @@ def fun(fps):
func_code = f.func_code # Python 2
else:
func_code = f.__code__ # Python 3

names = func_code.co_varnames[1:]

new_a = [fun(arg) if (name=='fps') else arg
for (arg, name) in zip(a, names)]
new_kw = {k: fun(v) if k=='fps' else v
for (k,v) in k.items()}

return f(clip, *new_a, **new_kw)


@decorator.decorator
def pygame_quit(f, *a, **k):
value = None
try:
value = f(*a, **k)
pg.mixer.quit()
pg.quit()
except pg.error:
pass
return value


34 changes: 18 additions & 16 deletions moviepy/video/io/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pygame as pg
import numpy as np

from moviepy.decorators import (requires_duration, convert_masks_to_RGB)
from moviepy.decorators import (requires_duration, convert_masks_to_RGB, pygame_quit)
from moviepy.tools import cvsecs


Expand All @@ -21,22 +21,22 @@ def imdisplay(imarray, screen=None):


@convert_masks_to_RGB
@pygame_quit
def show(clip, t=0, with_mask=True, interactive=False):
"""
Splashes the frame of clip corresponding to time ``t``.

Parameters
------------

t
Time in seconds of the frame to display.

with_mask
``False`` if the clip has a mask but you want to see the clip
without the mask.

"""

"""
if isinstance(t, tuple):
t = cvsecs(*t)

Expand Down Expand Up @@ -65,6 +65,7 @@ def show(clip, t=0, with_mask=True, interactive=False):

@requires_duration
@convert_masks_to_RGB
@pygame_quit
def preview(clip, fps=15, audio=True, audio_fps=22050, audio_buffersize=3000,
audio_nbytes=2, fullscreen=False):
"""
Expand All @@ -73,17 +74,17 @@ def preview(clip, fps=15, audio=True, audio_fps=22050, audio_buffersize=3000,
than normal, but it cannot avoid the clip to be played slower
than normal if the computations are complex. In this case, try
reducing the ``fps``.

Parameters
------------

fps
Number of frames per seconds in the displayed video.

audio
``True`` (default) if you want the clip's audio be played during
the preview.

audio_fps
The frames per second to use when generating the audio sound.

Expand All @@ -100,12 +101,12 @@ def preview(clip, fps=15, audio=True, audio_fps=22050, audio_buffersize=3000,
screen = pg.display.set_mode(clip.size, flags)

audio = audio and (clip.audio is not None)

if audio:
# the sound will be played in parrallel. We are not
# parralellizing it on different CPUs because it seems that
# pygame and openCV already use several cpus it seems.

# two synchro-flags to tell whether audio and video are ready
videoFlag = threading.Event()
audioFlag = threading.Event()
Expand All @@ -116,20 +117,21 @@ def preview(clip, fps=15, audio=True, audio_fps=22050, audio_buffersize=3000,
audio_nbytes,
audioFlag, videoFlag))
audiothread.start()

img = clip.get_frame(0)
imdisplay(img, screen)

if audio: # synchronize with audio
videoFlag.set() # say to the audio: video is ready
audioFlag.wait() # wait for the audio to be ready

result = []

t0 = time.time()
for t in np.arange(1.0 / fps, clip.duration-.001, 1.0 / fps):

img = clip.get_frame(t)

for event in pg.event.get():
if event.type == pg.QUIT or \
(event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ def run_tests(self):
'coveralls>=1.1,<2.0',
'pytest-cov>=2.5.1,<3.0',
'pytest>=3.0.0,<4.0',
'requests>=2.8.1,<3.0'
'requests>=2.8.1,<3.0',
'pygame>=1.9,<2'
]

extra_reqs = {
Expand Down