Demux Audio and Video From WebM

This topic describes how to use the Transcoder.run method to extract the first audio and video streams from a WebM container and save each stream into a separate WebM file.

The code snippets in this article are from the demux_webm_file Python sample.

Source File

For source we use the big-buck-bunny_trailer_vp8_vorbis.webm file from the AVBlocks Assets repository. After downloading and unzipping you will find it in the mov subdirectory.

Code

This code extracts the first audio and video elementary streams from a WebM container and writes them to separate WebM files.

Initialize AVBlocks

Initialize the AVBlocks library before creating the transcoder, and shut it down after the demuxing operation is complete.

Library.initialize()

# Set license information. Without this AVBlocks runs in Demo mode.
# Library.set_license("<license-string>")

result = demux_webm(input_file, output_base)

Library.shutdown()

sys.exit(0 if result else 2)

Read the Input Container

The sample uses the MediaInfo class to inspect the WebM input file and create the input socket from the discovered stream information.

media_info = MediaInfo()
media_info.inputs[0].file = input_file

if not media_info.open():
    print_error("MediaInfo open", media_info.error)
    return None

in_socket = MediaSocket.from_media_info(media_info)

media_info.close()

transcoder = Transcoder()
transcoder.allow_demo_mode = True
transcoder.inputs.add(in_socket)

Select Audio and Video Streams

The sample keeps the first audio stream and the first video stream. Any additional pins are disabled.

audio = False
video = False

for pin in in_socket.pins:
    stream_info = pin.stream_info

    if stream_info.media_type == MediaType.Audio and not audio:
        audio = True
        filename = output_base + ".aud.webm"
    elif stream_info.media_type == MediaType.Video and not video:
        video = True
        filename = output_base + ".vid.webm"
    else:
        # Disable this pin - we don't need other streams
        pin.connection = PinConnection.Disabled
        continue

    out_socket = MediaSocket()
    out_socket.pins.add(pin)
    delete_file(filename)
    out_socket.file = filename

    transcoder.outputs.add(out_socket)

    print(f"Output file: {filename}")

return transcoder

Run the Transcoder

After configuring the input and output sockets, the sample opens the transcoder, runs the demuxing operation, closes it, and returns the result.

def demux_webm(input_file: str, output_base: str) -> bool:
    """Demux WebM file into separate audio and video files."""
    # Create output directory if needed
    output_dir = os.path.dirname(output_base)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)

    transcoder = generate_output_transcoder(input_file, output_base)

    if transcoder is None:
        return False

    with transcoder:
        if not transcoder.open():
            print_error("Transcoder open", transcoder.error)
            return False

        if not transcoder.run():
            print_error("Transcoder run", transcoder.error)
            return False

        transcoder.close()

    return True

Complete Code

Here’s the complete working example that demonstrates WebM demuxing using AVBlocks for Python.

"""
Demux WebM file.

This sample demuxes a WebM file into separate audio and video WebM files.
"""

import os
import sys
import click

from avblocks import (
    Library, Transcoder, MediaInfo, MediaSocket,
    MediaType, ErrorFacility, PinConnection
)


def delete_file(filename: str):
    """Delete a file if it exists."""
    try:
        if os.path.exists(filename):
            os.remove(filename)
    except OSError:
        pass


def print_error(action: str, error) -> None:
    """Print error information."""
    if action:
        print(f"{action}: ", end="")

    if error.facility == ErrorFacility.Success:
        print("Success")
        return

    print(f"{error.message or ''}, facility:{error.facility} code:{error.code} hint:{error.hint or ''}")


def generate_output_transcoder(input_file: str, output_base: str) -> Transcoder:
    """Generate transcoder with output filenames based on stream types."""
    media_info = MediaInfo()
    media_info.inputs[0].file = input_file

    if not media_info.open():
        print_error("MediaInfo open", media_info.error)
        return None

    in_socket = MediaSocket.from_media_info(media_info)

    media_info.close()

    transcoder = Transcoder()
    transcoder.allow_demo_mode = True
    transcoder.inputs.add(in_socket)

    audio = False
    video = False

    for pin in in_socket.pins:
        stream_info = pin.stream_info

        if stream_info.media_type == MediaType.Audio and not audio:
            audio = True
            filename = output_base + ".aud.webm"
        elif stream_info.media_type == MediaType.Video and not video:
            video = True
            filename = output_base + ".vid.webm"
        else:
            # Disable this pin - we don't need other streams
            pin.connection = PinConnection.Disabled
            continue

        out_socket = MediaSocket()
        out_socket.pins.add(pin)
        delete_file(filename)
        out_socket.file = filename

        transcoder.outputs.add(out_socket)

        print(f"Output file: {filename}")

    return transcoder


def demux_webm(input_file: str, output_base: str) -> bool:
    """Demux WebM file into separate audio and video files."""
    # Create output directory if needed
    output_dir = os.path.dirname(output_base)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)

    transcoder = generate_output_transcoder(input_file, output_base)

    if transcoder is None:
        return False

    with transcoder:
        if not transcoder.open():
            print_error("Transcoder open", transcoder.error)
            return False

        if not transcoder.run():
            print_error("Transcoder run", transcoder.error)
            return False

        transcoder.close()

    return True


@click.command()
@click.option('-i', '--input', 'input_file',
              help='Input WebM file',
              type=click.Path(exists=True))
@click.option('-o', '--output', 'output_base',
              help='Output file base name (without extension)',
              type=click.Path())
def main(input_file: str, output_base: str):
    """Demux WebM file into separate audio and video files."""

    # Set default options if not provided
    if not input_file or not output_base:
        script_dir = os.path.dirname(os.path.abspath(__file__))

        if not input_file:
            input_file = os.path.join(script_dir, "../../assets/mov/big-buck-bunny_trailer_vp8_vorbis.webm")
        if not output_base:
            output_base = os.path.join(script_dir, "../../output/demux_webm_file/big-buck-bunny_trailer_vp8_vorbis")

        print("Using default options:")
        print(f"  --input {input_file}")
        print(f"  --output {output_base}")
        print()

    # Validate options
    print(f"Input file: {input_file}")
    print(f"Output file: {output_base}")

    Library.initialize()

    # Set license information. Without this AVBlocks runs in Demo mode.
    # Library.set_license("<license-string>")

    result = demux_webm(input_file, output_base)

    Library.shutdown()

    sys.exit(0 if result else 2)


if __name__ == '__main__':
    main()

How to Run

See the Python sample for details. The samples are installed as command-line tools when you install the package from the repository root with uv pip install --editable . (see Installation).

Command Line

demux-webm-file --input <webm file> --output <output filename without extension>

Examples

List options:

demux-webm-file --help
Usage: demux-webm-file [OPTIONS]

  Demux WebM file into separate audio and video files.

Options:
  -i, --input PATH   Input WebM file
  -o, --output PATH  Output file base name (without extension)
  --help             Show this message and exit.

Extract the audio and video streams from ./assets/mov/big-buck-bunny_trailer_vp8_vorbis.webm and save to ./output/demux_webm_file/big-buck-bunny_trailer_vp8_vorbis.aud.webm and ./output/demux_webm_file/big-buck-bunny_trailer_vp8_vorbis.vid.webm:

# Linux and macOS
mkdir -p ./output/demux_webm_file

demux-webm-file \
    --input ./assets/mov/big-buck-bunny_trailer_vp8_vorbis.webm \
    --output ./output/demux_webm_file/big-buck-bunny_trailer_vp8_vorbis
# Windows
New-Item -ItemType Directory -Force -Path ./output/demux_webm_file

demux-webm-file `
    --input ./assets/mov/big-buck-bunny_trailer_vp8_vorbis.webm `
    --output ./output/demux_webm_file/big-buck-bunny_trailer_vp8_vorbis