Skip to main content

Command-line media processing toolkit

Project description

Mediforge

Command-line media processing toolkit for video and audio composition. Mediforge replaces complex GUI applications (Shotcut, Ardour) for common linear media operations by orchestrating FFmpeg through human-readable YAML scenario files.

Installation

# Install in development mode
pip install -e ".[dev]"

# Or install from source
pip install .

Requirements

  • Python 3.11+
  • FFmpeg 5.0+ (including ffprobe)

Quick Start

# Get help
mediforge --help

# Show media file info
mediforge info video.mp4

# Compose video from scenario
mediforge video compose scenario.yaml -o output.mp4

# Compose audio from scenario
mediforge audio compose scenario.yaml -o output.wav

# Normalize audio loudness
mediforge audio normalize input.wav -o output.wav --lufs=-14

# Apply mastering preset
mediforge audio master input.wav -o output.wav --preset=streaming

# Mux video and audio
mediforge mux video.mp4 audio.wav -o output.mp4

Configuration

Configuration File Locations

Mediforge looks for configuration files in the following locations, in order of priority (highest first):

Priority Location Description
1 (highest) .mediforge.yaml Project-specific config in current directory
2 ~/.config/mediforge/config.yaml User-wide configuration
3 (lowest) Built-in defaults Hardcoded fallback values

Configuration values are deep-merged: project config overrides user config, which overrides defaults. You can override only specific values while inheriting others.

User Configuration File

Create ~/.config/mediforge/config.yaml to set user-wide defaults:

# ~/.config/mediforge/config.yaml

# Custom mastering presets (merged with built-in presets)
mastering_presets:
  my_podcast:
    target_lufs: -16.0
    true_peak: -1.5
    compressor:
      threshold: -18
      ratio: 3
      attack: 5
      release: 60
    eq:
      low_shelf:
        freq: 100
        gain: -3
      high_shelf:
        freq: 10000
        gain: 2

Project Configuration File

Create .mediforge.yaml in your project directory for project-specific settings:

# .mediforge.yaml

# Override or add presets for this project
mastering_presets:
  project_voice:
    target_lufs: -14.0
    true_peak: -1.0
    compressor:
      threshold: -16
      ratio: 4
      attack: 3
      release: 40

Scenario Files

Scenario files define timelines for video or audio composition. They use YAML format with timecode notation.

Video Scenario Example

# video_project.yaml
version: 1
type: video

output:
  resolution: 1920x1080    # Output resolution (WxH)
  framerate: 30            # Frames per second
  codec: h264              # Video codec (h264, h265, libx264, libx265)
  crf: 18                  # Quality (0-51, lower = better, 18 = visually lossless)
  preset: medium           # Encoding speed (ultrafast, fast, medium, slow, veryslow)

timeline:
  # Image clip with fade effects
  - start: "0:00:00.000"
    end: "0:00:05.000"
    type: image
    source: assets/title.png
    fade_in: 1.0           # Fade in duration (seconds)
    fade_out: 1.0          # Fade out duration (seconds)

  # Video clip with offset
  - start: "0:00:05.000"
    end: "0:00:35.000"
    type: video
    source: clips/interview.mp4
    offset: "0:00:10.000"  # Start from 10 seconds into source
    fade_in: 0.5
    fade_out: 0.5

  # Another video clip
  - start: "0:00:35.000"
    end: "0:01:00.000"
    type: video
    source: clips/broll.mp4
    speed: 1.5             # Playback speed (0.5 = half speed, 2.0 = double)

Audio Scenario Example

# audio_project.yaml
version: 1
type: audio

output:
  sample_rate: 48000       # Sample rate in Hz
  channels: 2              # Number of channels (1 = mono, 2 = stereo)
  codec: pcm_s24le         # Audio codec (pcm_s16le, pcm_s24le, mp3, aac)
  # bitrate: 320k          # For lossy codecs only

timeline:
  # Background music (quieter)
  - start: "0:00:00.000"
    end: "0:02:00.000"
    source: audio/background.wav
    volume: 0.3            # Volume level (1.0 = original, 0.5 = half)
    fade_in: 2.0
    fade_out: 2.0

  # Voiceover (full volume)
  - start: "0:00:05.000"
    end: "0:01:30.000"
    source: audio/voiceover.wav
    volume: 1.0
    fade_in: 0.5
    fade_out: 0.5

  # Sound effect
  - start: "0:00:30.000"
    end: "0:00:32.000"
    source: audio/swoosh.wav
    volume: 0.8

Timecode Format

Timecodes use the format H:MM:SS.mmm:

  • 1:30:00.000 = 1 hour, 30 minutes
  • 5:30.500 = 5 minutes, 30.5 seconds
  • 45.123 = 45.123 seconds
  • 0:00:00.000 = start of timeline

Clip Properties

Property Type Required Description
start timecode Yes Timeline start position
end timecode Yes Timeline end position
source path Yes Path to source file (relative to scenario file)
type string No video, audio, or image (auto-detected from extension)
offset timecode No Start position within source file (default: 0)
speed float No Playback speed multiplier (default: 1.0)
volume float No Volume level for audio (default: 1.0)
fade_in float No Fade-in duration in seconds
fade_out float No Fade-out duration in seconds

Commands Reference

Global Options

mediforge [OPTIONS] COMMAND [ARGS]...

Options:
  --log-level [error|warning|info|debug]  Logging verbosity level
  --version                               Show version and exit
  --help                                  Show help and exit

info - Display Media File Information

Probe a media file and display its metadata.

mediforge info [OPTIONS] FILE

Arguments:

Argument Description
FILE Path to media file (video, audio, or image)

Options:

Option Description
--json Output as JSON instead of formatted text
--help Show help

Examples:

# Display info in readable format
mediforge info video.mp4

# Output as JSON for scripting
mediforge info --json video.mp4 | jq '.duration'

Output includes:

  • File format and size
  • Duration and bitrate
  • Video stream: codec, resolution, frame rate, pixel format
  • Audio stream: codec, sample rate, channels

video - Video Processing Commands

video compose - Compose Video from Scenario

Build a video from a YAML scenario file containing timeline definitions.

mediforge video compose [OPTIONS] SCENARIO

Arguments:

Argument Description
SCENARIO Path to YAML scenario file

Options:

Option Description
-o, --output PATH Output video file path (required)
--dry-run Print FFmpeg commands without executing
--preview Generate low-quality preview (640x360, 15fps)
--progress Show progress bar during encoding
--keep-temp Preserve intermediate files after completion
--help Show help

Examples:

# Compose video
mediforge video compose project.yaml -o output.mp4

# Preview mode (faster, lower quality)
mediforge video compose project.yaml -o preview.mp4 --preview

# See what commands would run
mediforge video compose project.yaml -o output.mp4 --dry-run

# With progress bar
mediforge video compose project.yaml -o output.mp4 --progress

Supported input formats: .mp4, .mkv, .mov, .avi, .webm, .m4v, .png, .jpg, .jpeg, .webp, .tiff, .bmp, .gif


audio - Audio Processing Commands

audio compose - Compose Audio from Scenario

Build an audio file from a YAML scenario file.

mediforge audio compose [OPTIONS] SCENARIO

Arguments:

Argument Description
SCENARIO Path to YAML scenario file

Options:

Option Description
-o, --output PATH Output audio file path (required)
--dry-run Print FFmpeg commands without executing
--progress Show progress bar during encoding
--keep-temp Preserve intermediate files
--help Show help

Example:

mediforge audio compose audio_mix.yaml -o output.wav

audio normalize - Loudness Normalization

Normalize audio loudness using the EBU R128 standard. Performs two-pass processing for accurate results.

mediforge audio normalize [OPTIONS] INPUT_FILE

Arguments:

Argument Description
INPUT_FILE Input audio file

Options:

Option Description
-o, --output PATH Output audio file path (required)
-l, --lufs FLOAT Target integrated loudness in LUFS (default: -14)
-t, --true-peak FLOAT Maximum true peak in dB (default: -1)
-r, --sample-rate INT Output sample rate in Hz (default: 48000)
--dry-run Print FFmpeg commands without executing
--help Show help

Examples:

# Normalize to streaming standard (-14 LUFS)
mediforge audio normalize input.wav -o output.wav

# Normalize to broadcast standard (-23 LUFS)
mediforge audio normalize input.wav -o output.wav --lufs=-23

# Custom settings with sample rate
mediforge audio normalize input.wav -o output.wav --lufs=-16 --true-peak=-2 --sample-rate=44100

Common LUFS targets:

Platform Target LUFS
Spotify, YouTube, Apple Music -14
Podcasts -16
Broadcast TV (EBU R128) -23

audio master - Apply Mastering Preset

Apply a mastering chain (EQ, compression, limiting, normalization) using built-in or custom presets.

mediforge audio master [OPTIONS] INPUT_FILE

Arguments:

Argument Description
INPUT_FILE Input audio file

Options:

Option Description
-o, --output PATH Output audio file path (required)
-p, --preset NAME Mastering preset name (default: streaming)
-r, --sample-rate INT Output sample rate in Hz (default: 48000)
--list-presets List available presets and exit
--dry-run Print FFmpeg commands without executing
--help Show help

Examples:

# Apply streaming preset
mediforge audio master input.wav -o output.wav --preset=streaming

# Apply podcast preset with custom sample rate
mediforge audio master input.wav -o output.wav --preset=podcast --sample-rate=44100

# List all available presets
mediforge audio master input.wav -o out.wav --list-presets

Built-in Presets:

Preset Target LUFS Description
streaming -14 Optimized for Spotify, YouTube, Apple Music
podcast -16 Voice-focused with de-esser
broadcast -23 EBU R128 broadcast standard
music -14 Gentle compression for music
voice -16 Enhanced clarity for speech

mux - Combine Video and Audio

Multiplex (combine) a video file with an audio file, replacing any existing audio.

mediforge mux [OPTIONS] VIDEO AUDIO

Arguments:

Argument Description
VIDEO Input video file
AUDIO Input audio file

Options:

Option Description
-o, --output PATH Output file path (required)
--audio-offset FLOAT Audio offset in seconds (positive = delay audio)
--dry-run Print FFmpeg commands without executing
--help Show help

Examples:

# Simple mux
mediforge mux video.mp4 audio.wav -o output.mp4

# Delay audio by 0.5 seconds
mediforge mux video.mp4 audio.wav -o output.mp4 --audio-offset=0.5

# Preview command
mediforge mux video.mp4 audio.wav -o output.mp4 --dry-run

Developer Guide: Creating Plugins

Mediforge uses a plugin architecture that makes it easy to add new commands.

Plugin Structure

Plugins live in src/mediforge/plugins/. Each plugin is either:

  • A single Python file (e.g., mux/__init__.py)
  • A package directory with multiple commands

Plugin Interface

Every plugin must expose a register(parent: click.Group) function that adds commands to the CLI.

Simple Command Plugin

# src/mediforge/plugins/mycommand/__init__.py
"""My custom command plugin."""

from pathlib import Path
import click
from mediforge.utils.logging import get_logger


@click.command()
@click.argument('input_file', type=click.Path(exists=True, path_type=Path))
@click.option('-o', '--output', required=True, type=click.Path(path_type=Path),
              help='Output file path')
@click.option('--dry-run', is_flag=True, help='Print commands without executing')
@click.pass_context
def mycommand(ctx: click.Context, input_file: Path, output: Path, dry_run: bool) -> None:
    """
    Brief description of my command.

    Longer description and examples go here.
    """
    log_level = ctx.obj.get('log_level', 'warning') if ctx.obj else 'warning'
    logger = get_logger(__name__, log_level)

    logger.info(f"Processing: {input_file}")

    # Your implementation here
    if dry_run:
        print(f"Would process {input_file} -> {output}")
        return

    # ... actual processing ...


def register(parent: click.Group) -> None:
    """Register this command with the CLI."""
    parent.add_command(mycommand)

Command Group Plugin

For plugins with multiple subcommands:

# src/mediforge/plugins/mygroup/__init__.py
"""My command group plugin."""

import click
from . import subcommand1, subcommand2


@click.group()
def mygroup() -> None:
    """My group of commands."""
    pass


mygroup.add_command(subcommand1.cmd1)
mygroup.add_command(subcommand2.cmd2)


def register(parent: click.Group) -> None:
    """Register the command group with the CLI."""
    parent.add_command(mygroup)
# src/mediforge/plugins/mygroup/subcommand1.py
import click

@click.command('cmd1')
def cmd1():
    """First subcommand."""
    pass

Using Core Utilities

Plugins can use these core utilities:

# Logging
from mediforge.utils.logging import get_logger
logger = get_logger(__name__, log_level)
logger.debug("Debug message")
logger.info("Info message")

# Temporary directories
from mediforge.utils.tempfiles import TempDirectory
with TempDirectory(keep=keep_temp) as temp_dir:
    intermediate_file = temp_dir / "temp.mp4"
    # ... use temp_dir ...
# Auto-cleaned unless keep=True or error occurred

# FFmpeg execution
from mediforge.backends.executor import CommandExecutor
executor = CommandExecutor(dry_run=dry_run, log_level=log_level)
result = executor.execute(
    command=['ffmpeg', '-i', str(input_path), str(output_path)],
    stage="encoding",
    temp_dir=temp_dir,
)

# FFmpeg command building
from mediforge.backends.ffmpeg import FFmpegCommandBuilder
builder = FFmpegCommandBuilder()
cmd = builder.build_mux(video_path, audio_path, output_path)

# Error handling
from mediforge.core.errors import ValidationError, FFmpegError, MediaFileError
raise ValidationError("Invalid value", expected="number", found="text")

# Media probing
from mediforge.core.probe import get_media_info
info = get_media_info(Path("video.mp4"))
print(f"Duration: {info['duration']}")

Plugin Discovery

Plugins are automatically discovered at startup. The discovery process:

  1. Scans src/mediforge/plugins/ for Python modules/packages
  2. Skips modules starting with _
  3. Imports each module
  4. Calls register(parent) if the function exists

No manual registration required - just create your plugin in the plugins directory.


License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mediforge-0.7.6.tar.gz (62.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mediforge-0.7.6-py3-none-any.whl (39.8 kB view details)

Uploaded Python 3

File details

Details for the file mediforge-0.7.6.tar.gz.

File metadata

  • Download URL: mediforge-0.7.6.tar.gz
  • Upload date:
  • Size: 62.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for mediforge-0.7.6.tar.gz
Algorithm Hash digest
SHA256 38ba2e52c4d2866dc1359c9ad49f3a4c9feecea7e0845f607e800db53b0c269d
MD5 5d35ff4597db9ec6dc74833da0fef40a
BLAKE2b-256 6d83e7fdbc4f3b7b4bd63bab13353a847107742cb02282203cbc5abf24ed320b

See more details on using hashes here.

File details

Details for the file mediforge-0.7.6-py3-none-any.whl.

File metadata

  • Download URL: mediforge-0.7.6-py3-none-any.whl
  • Upload date:
  • Size: 39.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for mediforge-0.7.6-py3-none-any.whl
Algorithm Hash digest
SHA256 8b0e03c9804b4ca09f998692c0b032fda6ad34e8dd71597414051e24bedf4e29
MD5 ecbaf85ca18b93ebecd77f995212650f
BLAKE2b-256 142fd1148b1deb7acdda9f814d01bf43f2fc79eeeb650cedc69434056b6ae49f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page