From 34b52312ebb7c9bfb81b518da7c9b7cb71c21fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar?= Date: Fri, 21 May 2021 13:51:38 +0200 Subject: [PATCH 1/4] Add tests for 'audio_fadein' FX --- moviepy/audio/fx/audio_fadein.py | 46 +++++++++++++++++------- tests/test_fx.py | 60 ++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/moviepy/audio/fx/audio_fadein.py b/moviepy/audio/fx/audio_fadein.py index a0158cff5..e62c6412e 100644 --- a/moviepy/audio/fx/audio_fadein.py +++ b/moviepy/audio/fx/audio_fadein.py @@ -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, + ) diff --git a/tests/test_fx.py b/tests/test_fx.py index c06823692..431825bbd 100644 --- a/tests/test_fx.py +++ b/tests/test_fx.py @@ -19,6 +19,7 @@ ) from moviepy.audio.fx import ( audio_delay, + audio_fadein, audio_normalize, multiply_stereo_volume, multiply_volume, @@ -1279,5 +1280,64 @@ 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, 0.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 musted + 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 + + 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 + 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() From 844826e1d5297b51c4f0290bdfdde623e8b84f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar?= Date: Fri, 21 May 2021 13:58:01 +0200 Subject: [PATCH 2/4] Add time format test case for duration argument --- tests/test_fx.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_fx.py b/tests/test_fx.py index 431825bbd..9e7da2a00 100644 --- a/tests/test_fx.py +++ b/tests/test_fx.py @@ -24,6 +24,7 @@ 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, @@ -1287,7 +1288,7 @@ def test_audio_delay(duration, offset, n_repeats, decay): ( ( (0.2, 0.1), - (1, 0.4), + (1, "00:00:00,4"), (0.3, 0.13), ) ), @@ -1312,6 +1313,8 @@ def test_audio_fadein(sound_type, fps, clip_duration, fadein_duration): 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 From a894f23b550a277f4200a95b3947c92131c9ceaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar?= Date: Fri, 21 May 2021 14:06:21 +0200 Subject: [PATCH 3/4] Fix comments in tests --- tests/test_fx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_fx.py b/tests/test_fx.py index 9e7da2a00..3de4f55d3 100644 --- a/tests/test_fx.py +++ b/tests/test_fx.py @@ -1304,7 +1304,7 @@ def test_audio_fadein(sound_type, fps, clip_duration, fadein_duration): clip = AudioClip(make_frame, duration=clip_duration, fps=fps) new_clip = audio_fadein(clip, fadein_duration) - # first frame is musted + # first frame is muted first_frame = new_clip.get_frame(0) if sound_type == "stereo": assert len(first_frame) > 1 @@ -1332,7 +1332,7 @@ def test_audio_fadein(sound_type, fps, clip_duration, fadein_duration): ] # cut non transformed part into subclips and check the expected max_volume - # for each one + # 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): From 75db4662bbb537c2bf57d77b7cd0485786f70129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar?= Date: Fri, 21 May 2021 14:28:27 +0200 Subject: [PATCH 4/4] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b4126dd..c8064e331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)