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 minutes5:30.500= 5 minutes, 30.5 seconds45.123= 45.123 seconds0: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:
- Scans
src/mediforge/plugins/for Python modules/packages - Skips modules starting with
_ - Imports each module
- Calls
register(parent)if the function exists
No manual registration required - just create your plugin in the plugins directory.
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38ba2e52c4d2866dc1359c9ad49f3a4c9feecea7e0845f607e800db53b0c269d
|
|
| MD5 |
5d35ff4597db9ec6dc74833da0fef40a
|
|
| BLAKE2b-256 |
6d83e7fdbc4f3b7b4bd63bab13353a847107742cb02282203cbc5abf24ed320b
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b0e03c9804b4ca09f998692c0b032fda6ad34e8dd71597414051e24bedf4e29
|
|
| MD5 |
ecbaf85ca18b93ebecd77f995212650f
|
|
| BLAKE2b-256 |
142fd1148b1deb7acdda9f814d01bf43f2fc79eeeb650cedc69434056b6ae49f
|