Automatically play sound in IPython notebook

Here is another version (Python-side mostly) which works well with JupyterLab:

from time import time
from IPython import get_ipython
from IPython.display import Audio, display


class Beeper:

    def __init__(self, threshold, **audio_kwargs):
        self.threshold = threshold
        self.start_time = None    # time in sec, or None
        self.audio = audio_kwargs

    def pre_execute(self):
        if not self.start_time:
            self.start_time = time()

    def post_execute(self):
        end_time = time()
        if self.start_time and end_time - self.start_time > self.threshold:
            audio = Audio(**self.audio, autoplay=True)
            display(audio)
        self.start_time = None


beeper = Beeper(5, url='http://www.soundjay.com/button/beep-07.wav')

ipython = get_ipython()
ipython.events.register('pre_execute', beeper.pre_execute)
ipython.events.register('post_execute', beeper.post_execute)

The beep will automatically be emitted after each code execution which took more than 5 seconds, but the consecutive executions are not counted together.

For example:

# cell 0:
from time import sleep
# cell 1:
sleep(6)    # will ring

If you then add another cell

# cell 3:
sleep(3)    # it won't ring

Tested with JupyterLab 0.32.1 and Jupyter notebook 5.5.0.

Edit: to reduce the clutter of the shown audio players I use following snippet (for Python older than 3.6 you need to use .format() instead of f-strings):

from IPython.display import Audio, display


class InvisibleAudio(Audio):
    def _repr_html_(self):
        audio = super()._repr_html_()
        audio = audio.replace('<audio', f'<audio onended="this.parentNode.removeChild(this)"')
        return f'<div style="display:none">{audio}</div>'

and then use InvisibleAudio instead of Audio in post_execute.


My favorite solution (no need for an external module) :

import os
os.system("printf '\a'") # or '\7'

Works on OS X.

However DaveP's remark still apply : it is not the browser playing the sound but the server.


TL;DR

At the top of your notebook

from IPython.display import Audio
sound_file = './sound/beep.wav'

sound_file should point to a file on your computer, or accessible from the internet.

Then later, at the end of the long-running cell

<code that takes a long time>

Audio(sound_file, autoplay=True)

This method uses the Audio tag built into Newer versions of iPython/Jupyter.

Note For Older Versions

Older versions without the Audio tag can use the following method.

Put this in a cell and run it before you want to play your sound:

from IPython.display import HTML
from base64 import b64encode

path_to_audio = "/path/to/snd/my-sound.mp3"
audio_type = "mp3"

sound = open(path_to_audio, "rb").read()
sound_encoded = b64encode(sound)
sound_tag = """
    <audio id="beep" controls src="data:audio/{1};base64,{0}">
    </audio>""".format(sound_encoded, audio_type)

play_beep = """
<script type="text/javascript">
    var audio = document.getElementById("beep");
    audio.play();
</script>
"""

HTML(sound_tag)

At the end of the cell you want to make a noise on completion put this:

HTML(play_beep)

How it works:

It reads a file from the filesystem using iPython's built in open and read methods. Then it encodes this into base64. It then creates an audio tag with the ID beep and injects the base64 data into it. The final piece of setup creates a small script tag that plays the sound.

This method should work in any browser that supports the HTML5 audio tag.

Note: if you'd rather not display the audio controls in your notebook, just remove the controls attribute from the variable named sound_tag