Audio And Sound Effects
Choose the correct scene-timeline audio channel, mix voiceover, background audio, and secure bundled effects into one MP4, and browse every available sound ID.
Choose An Audio Channel
Audio is classified by purpose and timing. Pick the channel first; the renderer keeps its metadata separate in the editor and combines all active channels into the final MP4 mix.
| Use case | API | Timing | Editor color |
|---|---|---|---|
| Spoken narration and captions | with self.play_voiceover_block(...) | Starts at block entry and can pad scene time to the narration duration. | Sky blue |
| Music, ambience, or a continuous bed | with self.play_background_audio_block(...) | Covers exactly the with-block span; supports looping, volume, and fades. | Violet |
| A built-in click, impact, ambience, music track, or other packaged cue | with self.play_sound("category/name") | Starts at block entry and, by default, is bounded by the with-body. | Emerald green |
VideoMobject audio and writes the combined stream into the MP4. The timeline retains the separate cue types so timing remains understandable.Voiceovers
Use a voiceover scene class when spoken narration should own pacing or captions. Define stable keys withset_voiceovers, then play a key in a context. The tracker exposes the measuredduration, and the scene is padded when narration outlasts the animations in its block.
from manim import * class NarratedOrbitScene(VoiceoverThreeDScene): def construct(self): self.set_voiceovers({ "intro": { "text": "Reusable narration on top of a Three D scene.", "mp3": "/data/audio/intro.mp3", }, }) sphere = Sphere(radius=1.2) with self.play_voiceover_block("intro") as tracker: self.play(Create(sphere), run_time=min(1.0, tracker.duration)) Choose VoiceoverScene, VoiceoverMovingCameraScene, orVoiceoverThreeDScene to match the visual scene type. Upload or generate narration first and bind the resulting workspace path. Do not use bundled sound effects for speech.
Background Audio
Use background audio for music or ambience that should cover a known scene span. It works on base and voiceover scene classes. The source is an uploaded workspace asset or workspace-confined audio path.
from manim import * class BackgroundAudioScene(Scene): def construct(self): with self.play_background_audio_block( "/data/audio/theme.mp3", volume=0.25, fade_in=0.2, fade_out=0.3, loop=True, ): self.play(Create(Circle()), run_time=2) self.wait(1) loop=True repeats the source to cover the block. Without looping, playback stops when the source ends. A leading slash means the root of the current user workspace—not the server filesystem. Targets outside that workspace and symlink escapes are rejected.
Sound Effects
Use self.play_sound for a trusted, package-owned sound, including both short effects and the longer documentary tracks below. It works on Scene, ThreeDScene, moving-camera scenes, and every voiceover variant. It does not accept uploaded files, paths, URLs, or raw audio data—only an exact catalog ID.
from manim import * class SoundEffectScene(Scene): def construct(self): button = RoundedRectangle(width=3.2, height=1.2) label = Text("Launch").move_to(button) self.add(button, label) # Without duration, the with-body is the playback boundary. A longer # sound is trimmed; a shorter sound ends naturally. with self.play_sound("ui/click", volume=0.8) as cue: self.play(button.animate.set_fill(BLUE, opacity=0.35), run_time=0.4) with self.play_sound("movement/slide", fade_out=0.05): self.play(VGroup(button, label).animate.shift(UP * 2), run_time=0.8) # duration requests an audible span. Extending beyond the source # requires loop=True. with self.play_sound( "ui/click", duration=1.2, loop=True, fade_in=0.15, fade_out=0.25, ): self.play(button.animate.set_stroke(YELLOW), run_time=1.2) # The sound can continue over later scene work without padding this # block when asynchronous timing is intentional. with self.play_sound("ui/confirm", duration=0.5, pad_block=False): self.wait(0.02) Without an explicit duration, the with-body is authoritative: a longer source is trimmed, a shorter source ends naturally, and loop=True repeats a shorter source through the body. This default never adds a sound-driven wait. An explicit duration sets the requested playback span and may outlast the body. If it also outlasts the source, only loop=True repeats the source; otherwise playback stops at the source's natural end with no fake silent tail. pad_block=True waits for explicit playback that actually outlasts the body, while pad_block=False lets it continue asynchronously.
| duration | loop | Playback duration | Block behavior |
|---|---|---|---|
| Omitted | False | Source or with-body, whichever ends first | Never adds a sound-driven wait. |
| Omitted | True | Exact with-body duration | Trims or repeats to the body end; adds no wait. |
| Explicit, within source | Either | Requested duration | Pads only when playback outlasts the body. |
| Explicit, beyond source | False | Natural source duration | Stops naturally; no silent tail is reported. |
| Explicit, beyond source | True | Requested duration | Repeats from the beginning to the exact duration. |
Nature effects
from manim import * class NatureSoundScene(Scene): def construct(self): feather = TablerIcon("feather", color=YELLOW).scale(1.2) drop = Dot(color=BLUE).shift(UP * 2) with self.play_sound("nature/wind/gust", volume=0.7): self.play(FadeIn(feather), run_time=0.5) with self.play_sound("nature/water/drop"): self.play(drop.animate.shift(DOWN * 4), run_time=0.7) with self.play_sound("nature/weather/thunder", fade_in=0.05): self.play(Flash(ORIGIN, color=WHITE), run_time=0.4) loop=Trueconstrains them to the block. Use an uploaded rain asset with play_background_audio_block when the bundled catalog does not contain the recording you need.This nature example is also kept as the runnable renderer fixturepackages/manim_renderer/tests/fixtures/nature_sound_effect_scene.py so the documented API is exercised by real MP4 verification.
| Argument | Contract |
|---|---|
| name | Required exact lowercase hierarchical ID such as ui/click or impact/light. |
| volume | Finite value from 0 through 4; defaults to 1. |
| fade_in / fade_out | Each is 0 through 10 seconds and cannot exceed playback duration. They may overlap; fades apply once at the overall beginning and end. |
| duration | Optional finite value greater than 0 and at most 3600 seconds. When omitted, the with-body is the playback boundary. |
| loop | Defaults to False. Repeats only when the body or explicit duration extends beyond the source. Restarting is exact but does not crossfade a non-loop-ready source. |
| pad_block | Defaults to True. Waits only when explicit playback actually outlasts the with-body. False lets following scene work begin while audio continues. |
| context result | source_duration is fixed; playback_duration/final_duration, body_duration, and block_duration expose the resolved timing. |
Unknown well-formed IDs produce a compile error with close-name suggestions. Malformed input—including an extension, whitespace, traversal segment, URL, markup, shell fragment, non-string value, or string subclass—is rejected before FFmpeg runs. Without an explicit duration, playback depends on the completed with-body, so fade fit is validated when that body exits.
Documentary Library
The documentary/* namespace is a built-in CC0 collection for serious science, space, technology, and reflective storytelling. Use it only through play_sound. The separate background-audio API continues to resolve user-owned workspace audio and does not expose these packaged files.
from manim import * class ComputationDocumentary(Scene): def construct(self): title = Text("What Is Computation?") # With no duration, this longer built-in bed ends exactly with the body. with self.play_sound( "documentary/music/serious-ambient", volume=0.25, fade_in=1.0, fade_out=2.0, ): self.play(Write(title), run_time=2) self.wait(3) with self.play_sound("documentary/transition/whoosh", volume=0.35): self.play(title.animate.scale(0.85).to_edge(UP), run_time=0.6) with self.play_sound("documentary/impact/low-cinematic-boom", volume=0.3): self.play(FadeIn(Text("Universe → Information → Computation"))) | Core collection | Vocabulary | Use it for |
|---|---|---|
| Existing Kenney + 100 CC0 SFX #2 bundle | Digital, UI, glitches, impacts, sci-fi, low-frequency explosions | Computer, transistor, information, and transition details |
| Owlish Media Sound Effects Pack | 161 ambience, cloth, footsteps, human, impact, paper, sci-fi, technology, UI, and water recordings | Earth, laboratories, hardware, interfaces, and documentary foley |
| 50 CC0 Sci-Fi SFX | 50 beeps, machines, terminal cues, loops, teleports, and unusual electronic textures | Computation, computers, data, and technology atmosphere |
| Documentary music collection | Ten space, mystery, reflection, and slow-tension tracks from Joth, yd, Ruskerdax, and TinyWorlds | Universe, physics, entropy, intellectual curiosity, and philosophical pacing |
| Cinematic transition collection | Two appliance drones, 13 swishes, a scientific sting, and a low boom | Risers, whooshes, chapter transitions, reveals, and restrained emphasis |
A short built-in documentary cue is also kept as the runnable renderer fixturepackages/manim_renderer/tests/fixtures/documentary_sound_effect_scene.py and is used for real MP4 audio verification.
Verified original sources
Every source below is the original uploader's download and license page. Each is marked CC0 1.0: commercial use, editing, looping, layering, remixing, worldwide distribution, and monetized-video use are permitted; attribution and payment are not required. Formats and byte sizes describe the exact pinned downloads used by the generator, not an unrelated preview.
Sound ID Taxonomy
There are two intentional naming layers. Canonical IDs retain the pinned source-pack namespace, making every upstream file addressable without a handwritten Python subset. Semantic aliases provide stable intent-based names for generated scene code.
| Layer | Examples | When to use it |
|---|---|---|
| Canonical pack IDs | interface/confirmation-001, general/door-01, impact/footstep-grass-000 | Use when selecting an exact variation from a complete source pack. |
| Curated nature IDs | nature/bird/chirp, nature/water/drop, nature/wind/soft | Use real pinned CC0 environmental one-shots without uploading a file. |
| Documentary canonical IDs | documentary/music/yd/out-there, documentary/rubberduck-sci-fi/terminal-01 | Use an exact music, ambience, technology, foley, or transition asset from the verified documentary collection. |
| Documentary semantic aliases | documentary/music/serious-ambient, documentary/transition/whoosh | Prefer for durable generated documentary code when a stable intent-based default is enough. |
| Semantic aliases | ui/confirm, nature/weather/thunder, movement/slide, impact/light | Prefer for durable generated code when one stable default is enough. |
from manim import * print(sound_effect_count()) # 869 source assetsprint(len(sound_effect_names())) # 899 names, including stable aliases for name in sound_effect_names(): if name.startswith("interface/"): print(name) _SOUND_EFFECTS runtime dictionary. “Complete” means every real audio file in each named bundled pack is present; it does not claim to contain every sound published on the internet.Combine All Three
Contexts may be nested. Each records its own start time and timing rule; the final mixer aligns all executed cues against the scene timeline.
from manim import * class ThreeAudioChannelsScene(VoiceoverScene): def construct(self): self.set_voiceovers({ "intro": { "text": "Three independent audio channels, one final video.", "mp3": "/data/audio/narration.ogg", }, }) title = Text("Three audio channels") with self.play_background_audio_block( "/data/audio/theme.ogg", volume=0.2, fade_out=0.15, loop=True, ): with self.play_voiceover_block("intro"): with self.play_sound("ui/confirm", volume=0.7): self.play(Write(title), run_time=0.6) with self.play_sound("impact/light"): self.play(title.animate.scale(1.1), run_time=0.4) This exact pattern is kept as the runnable renderer fixturepackages/manim_renderer/tests/fixtures/three_audio_channels_scene.py. Replace its two workspace paths with uploaded narration and background files; bundled effect IDs need no upload.
Security, Packaging, And Performance
| Concern | Behavior |
|---|---|
| Path and command injection | Scene code supplies only a strict allowlisted ID. The final mixer resolves it again under the package-owned asset root and ignores scene-provided paths or URLs. |
| Source repository size | Generated audio files and catalog JSON are ignored by Git and excluded from the Docker source context. Only the generator, pinned checksums, provenance, runtime, tests, and docs are source. |
| Installation | The first local build, test, or dev command fills the ignored cache automatically. Docker downloads assets in a dedicated cacheable stage, keeps a checksum-addressed BuildKit download cache across generator changes, and re-verifies bytes before reuse. Production needs no separate download. |
| Runtime compile cost | Scenes without effects do not parse the catalog. The 899-name catalog is loaded once per worker process; later valid resolutions reuse the in-memory dictionary and still verify that the resolved package file exists. |
| Search space | Exact IDs use an O(1) dictionary lookup. The 899-name similarity scan runs only for an unknown ID and is never part of a valid render. |
The generated catalog currently contains 266.7 MiB of original audio. Runtime compilation performs no network request. Archive checksums, direct-file checksums, per-file checksums, durations, count, aliases, and file existence are verified while generating the image.
Complete Sound Catalog
Filter by ID, source, or alias target. Click any body cell to copy that row's ID. The bounded table scrolls independently and keeps its header visible. It is generated at static-doc build time from the same checked manifest used by the Python renderer, so documentation and the container catalog stay aligned.
| Sound ID | Type | Resolves to | Source |
|---|---|---|---|
Click any body cell to copy that row’s sound ID.