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 'AudioClip.max_volume(stereo=True)' support for more than 2 channels #1464

Merged
merged 2 commits into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Changed deprecated `tostring` method by `tobytes` in `video.io.gif_writers::write_gif` [\#1429](https://github.com/Zulko/moviepy/pull/1429)
- Fixed calling `audio_normalize` on a clip with no sound causing `ZeroDivisionError` [\#1401](https://github.com/Zulko/moviepy/pull/1401)
- Fixed `freeze` FX was freezing at time minus 1 second as the end [\#1461](https://github.com/Zulko/moviepy/pull/1461)
- `AudioClip.max_volume(stereo=True)` now can return more than 2 channels [\#1464](https://github.com/Zulko/moviepy/pull/1464)


## [v2.0.0.dev2](https://github.com/zulko/moviepy/tree/v2.0.0.dev2) (2020-10-05)
Expand Down
17 changes: 8 additions & 9 deletions moviepy/audio/AudioClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,17 +157,16 @@ def to_soundarray(
return snd_array

def max_volume(self, stereo=False, chunksize=50000, logger=None):
# max volume separated by channels if ``stereo`` and not mono
stereo = stereo and self.nchannels > 1

stereo = stereo and (self.nchannels == 2)

maxi = np.array([0, 0]) if stereo else 0
# zero for each channel
maxi = np.zeros(self.nchannels)
for chunk in self.iter_chunks(chunksize=chunksize, logger=logger):
maxi = (
np.maximum(maxi, abs(chunk).max(axis=0))
if stereo
else max(maxi, abs(chunk).max())
)
return maxi
maxi = np.maximum(maxi, abs(chunk).max(axis=0))

# if mono returns float, otherwise array of volumes by channel
return maxi if stereo else maxi[0]

@requires_duration
@convert_path_to_string("filename")
Expand Down
46 changes: 46 additions & 0 deletions tests/test_AudioClips.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,51 @@ def test_audiofileclip_concat():
concat.write_audiofile(os.path.join(TMP_DIR, "concat_audio_file.mp3"))


def test_audioclip_mono_max_volume():
# mono
make_frame_440 = lambda t: np.sin(440 * 2 * np.pi * t)
clip = AudioClip(make_frame_440, duration=1, fps=44100)
max_volume = clip.max_volume()
assert isinstance(max_volume, float)
assert max_volume > 0


@pytest.mark.parametrize(("nchannels"), (2, 4, 8, 16))
@pytest.mark.parametrize(("channel_muted"), ("left", "right"))
def test_audioclip_stereo_max_volume(nchannels, channel_muted):
def make_frame(t):
frame = []
# build channels (one of each pair muted)
for i in range(int(nchannels / 2)):
if channel_muted == "left":
# if muted channel is left, [0, sound, 0, sound...]
frame.append(np.sin(t * 0))
frame.append(np.sin(440 * 2 * np.pi * t))
else:
# if muted channel is right, [sound, 0, sound, 0...]
frame.append(np.sin(440 * 2 * np.pi * t))
frame.append(np.sin(t * 0))
return np.array(frame).T

clip = AudioClip(make_frame, fps=44100, duration=1)
max_volume = clip.max_volume(stereo=True)
# if `stereo == True`, `AudioClip.max_volume` returns a Numpy array`
assert isinstance(max_volume, np.ndarray)
assert len(max_volume) == nchannels

# check channels muted and with sound
for i, channel_max_volume in enumerate(max_volume):
if i % 2 == 0:
if channel_muted == "left":
assert channel_max_volume == 0
else:
assert channel_max_volume > 0
else:
if channel_muted == "right":
assert channel_max_volume == 0
else:
assert channel_max_volume > 0


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