Skip to content

Commit

Permalink
Add tests for 'audio_fadein' FX (#1574)
Browse files Browse the repository at this point in the history
* Add tests for 'audio_fadein' FX

Refactored audio_fadein to improve performance.
Fix mono clips crashing when audio_fadein FX applied.
Add support to pass duration parameter as time format (AA:BB:CC, tuples...).
Document it properly.
  • Loading branch information
mondeja authored May 21, 2021
1 parent 5cca162 commit b330dbe
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed rotate FX not being applied to mask images [\#1399](https://github.com/Zulko/moviepy/pull/1399)
- Fixed opacity error blitting VideoClips [\#1552](https://github.com/Zulko/moviepy/pull/1552)
- Fixed rotation metadata of input not being taken into account rendering VideoClips [\#577](https://github.com/Zulko/moviepy/pull/577)
- Fixed mono clips crashing when `audio_fadein` FX applied [\#1574](https://github.com/Zulko/moviepy/pull/1574)


## [v2.0.0.dev2](https://github.com/zulko/moviepy/tree/v2.0.0.dev2) (2020-10-05)
Expand Down
46 changes: 34 additions & 12 deletions moviepy/audio/fx/audio_fadein.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
import numpy as np

from moviepy.decorators import audio_video_fx
from moviepy.decorators import audio_video_fx, convert_parameter_to_seconds


def _mono_factor_getter():
return lambda t, duration: np.minimum(t / duration, 1)


def _stereo_factor_getter(nchannels):
def getter(t, duration):
factor = np.minimum(t / duration, 1)
return np.array([factor for _ in range(nchannels)]).T

return getter


@audio_video_fx
@convert_parameter_to_seconds(["duration"])
def audio_fadein(clip, duration):
"""Return an audio (or video) clip that is first mute, then the
sound arrives progressively over ``duration`` seconds.
"""
def fading(get_frame, t):
frame = get_frame(t)
Parameters
----------
duration : float
How long does it take for the sound to return to its normal level.
if np.isscalar(t):
factor = min(1.0 * t / duration, 1)
factor = np.array([factor, factor])
else:
factor = np.minimum(1.0 * t / duration, 1)
factor = np.vstack([factor, factor]).T
return factor * frame
Examples
--------
>>> clip = VideoFileClip("media/chaplin.mp4")
>>> clip.fx(audio_fadein, "00:00:06")
"""
get_factor = (
_mono_factor_getter()
if clip.nchannels == 1
else _stereo_factor_getter(clip.nchannels)
)

return clip.transform(fading, keep_duration=True)
return clip.transform(
lambda get_frame, t: get_factor(t, duration) * get_frame(t),
keep_duration=True,
)
63 changes: 63 additions & 0 deletions tests/test_fx.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
)
from moviepy.audio.fx import (
audio_delay,
audio_fadein,
audio_normalize,
multiply_stereo_volume,
multiply_volume,
)
from moviepy.tools import convert_to_seconds
from moviepy.utils import close_all_clips
from moviepy.video.fx import (
blackwhite,
Expand Down Expand Up @@ -1279,5 +1281,66 @@ def test_audio_delay(duration, offset, n_repeats, decay):
)


@pytest.mark.parametrize("sound_type", ("stereo", "mono"))
@pytest.mark.parametrize("fps", (44100, 22050))
@pytest.mark.parametrize(
("clip_duration", "fadein_duration"),
(
(
(0.2, 0.1),
(1, "00:00:00,4"),
(0.3, 0.13),
)
),
)
def test_audio_fadein(sound_type, fps, clip_duration, fadein_duration):
if sound_type == "stereo":
make_frame = lambda t: np.array(
[np.sin(440 * 2 * np.pi * t), np.sin(160 * 2 * np.pi * t)]
).T.copy(order="C")
else:
make_frame = lambda t: np.sin(440 * 2 * np.pi * t)

clip = AudioClip(make_frame, duration=clip_duration, fps=fps)
new_clip = audio_fadein(clip, fadein_duration)

# first frame is muted
first_frame = new_clip.get_frame(0)
if sound_type == "stereo":
assert len(first_frame) > 1
for value in first_frame:
assert value == 0.0
else:
assert first_frame == 0.0

fadein_duration = convert_to_seconds(fadein_duration)

n_parts = 10

# cut transformed part into subclips and check the expected max_volume for
# each one
time_foreach_part = fadein_duration / n_parts
start_times = np.arange(0, fadein_duration, time_foreach_part)
for i, start_time in enumerate(start_times):
end_time = start_time + time_foreach_part
subclip_max_volume = new_clip.subclip(start_time, end_time).max_volume()

possible_value = (i + 1) / n_parts
assert round(subclip_max_volume, 2) in [
possible_value,
round(possible_value - 0.01, 5),
]

# cut non transformed part into subclips and check the expected max_volume
# for each one (almost 1)
time_foreach_part = (clip_duration - fadein_duration) / n_parts
start_times = np.arange(fadein_duration, clip_duration, time_foreach_part)
for i, start_time in enumerate(start_times):
end_time = start_time + time_foreach_part
subclip_max_volume = new_clip.subclip(start_time, end_time).max_volume()

assert round(subclip_max_volume, 4) == 1


if __name__ == "__main__":
pytest.main()

0 comments on commit b330dbe

Please sign in to comment.