media-engine

media-engine

MCP server exposing headless ffmpeg operations for video, audio, and image processing via auto-generated typed tools, enabling agentic media file manipulation.

Category
访问服务器

README

<p align="center"> <img src="assets/banner.svg" alt="media·engine — headless ffmpeg operations. one engine, three faces, zero dependencies." width="100%"> </p>

<p align="center"> <a href="LICENSE"><img alt="License: GPL-3.0" src="https://img.shields.io/badge/license-GPL--3.0-a78bfa?style=flat-square"></a> <img alt="Python 3.9+" src="https://img.shields.io/badge/python-3.9%2B-5ff0d8?style=flat-square"> <img alt="Runtime dependencies: zero" src="https://img.shields.io/badge/dependencies-zero-5ff0d8?style=flat-square"> <img alt="ffmpeg 4.4+" src="https://img.shields.io/badge/ffmpeg-4.4%2B-2f3540?style=flat-square"> </p>

A standalone, headless media-processing engine: 19 ffmpeg recipes for video, audio, and still images, defined once in a single operations registry and driven through three interchangeable faces — a CLI, an MCP server, and a drag-and-drop web UI. The engine and two of the three faces are pure Python stdlib; ffmpeg is the only external binary. Everything runs locally — your files never leave your machine.

<p align="center"> <img src="assets/screenshot.png" alt="media-engine web UI running the video_to_gif operation, default palette" width="49%"> <img src="assets/screenshot-hue.png" alt="the same page with the hue knob rotated to a warm palette" width="49%"> </p> <p align="center"><sub>The web UI running <code>video_to_gif</code> — drag a file, tune the auto-generated form, watch ffmpeg run live. <em>Same page, hue knob rotated:</em> every accent is one CSS variable and shader/canvas uniform, so the whole palette and the animated background recolor instantly (and the motion can be paused to save resources).</sub></p>

<p align="center"><sub><em>Dogfood note:</em> these screenshots were downscaled for the repo with media-engine itself —<br> <code>python3 cli.py run resize_image -i shot.png -o assets/screenshot.png --imageResizePreset manual --imageCustomWidth 1200 --imageCustomHeight 550</code></sub></p>

                      ┌──────────────────────────────┐
  cli.py ────────────►│                              │
  server.py (MCP) ───►│   OPERATIONS registry (19)   ├──► ffmpeg
  webserver.py ──────►│   validate → probe → build   │
                      └──────────────────────────────┘

Add an operation once and it appears in all three faces automatically — as a CLI subcommand, as a typed MCP tool, and as an auto-generated web form. Zero per-face code.

Contents

Highlights

  • One registry, three faces. Every operation is a declarative Operation entry (typed params, an ffmpeg command builder, an output-extension rule). The CLI, the MCP server, and the web UI are thin projections of that one registry.
  • Zero dependencies. The engine, the CLI, and the web UI need nothing but Python and ffmpeg — no pip install, no venv, no build step. Only the MCP face pulls in one package (fastmcp).
  • Runs on old and new ffmpeg. A version probe picks -fps_mode (ffmpeg ≥ 5) or the legacy -vsync automatically, so the same code runs on a stock Ubuntu ffmpeg 4.4 and on current builds. All 19 operations are verified end-to-end on ffmpeg 4.4.2.
  • Honest progress. Progress comes from ffmpeg's machine-readable -progress pipe:1 stream, not from scraping locale-dependent stderr — it feeds the CLI progress line and the web UI's live SSE updates alike.
  • Local and private. No network calls, no telemetry, no cloud. The web UI binds 127.0.0.1 by default and processes uploads in throwaway temp workdirs.

Quick start

git clone https://github.com/constant-itis/media-engine.git
cd media-engine

ffmpeg -version        # any reasonably recent build; 4.4+ verified

# CLI — no install at all
python3 cli.py list
python3 cli.py run video_to_gif -i clip.mp4 -o clip.gif

# Web UI — no install either
python3 webserver.py   # then open http://127.0.0.1:8765

# MCP server — the one face with a dependency
pip install -r requirements.txt
python3 server.py

If ffmpeg/ffprobe aren't on PATH, point at them with the FFMPEG_BIN / FFPROBE_BIN environment variables.

The three faces

CLI

python3 cli.py list                 # all operations, one line each
python3 cli.py info <op>            # params, types, ranges, defaults
python3 cli.py run  <op> -i IN -o OUT [--param value ...]

Real examples:

# two-pass palette GIF at 15 fps, 480p
python3 cli.py run video_to_gif  -i clip.mp4  -o clip.gif  --gifFpsPreset 15 --gifResolutionPreset 480p

# 50% faster, video and audio kept in sync
python3 cli.py run video_speed   -i clip.mp4  -o fast.mp4  --speed 50

# transcode to opus with EBU R128 loudness normalization
python3 cli.py run convert_audio -i song.wav  -o song.opus --audioFormat opus --normalizeLoudness true

# watermark, bottom-right, 18% of frame width
python3 cli.py run add_watermark -i clip.mp4  -o marked.mp4 --coverImage logo.png --watermarkPosition bottom-right

# print the exact ffmpeg command(s) without running them
python3 cli.py run video_to_gif  -i clip.mp4  -o clip.gif  --dry-run

Every param takes a value (booleans included: --gifExportWebp true). run also accepts --threads N, --quiet, and --dry-run — dry-run prints the fully-assembled ffmpeg invocations, which doubles as a recipe reference.

MCP server

server.py exposes the registry over the Model Context Protocol as a stdio server — one typed tool per operation, auto-generated from the same OPERATIONS registry. Choice params become enum-constrained schema fields, numeric ranges and defaults carry over, output_path is optional (derived next to the input as {stem}_{op}.{ext}), and every tool accepts dry_run. A list_operations tool covers discovery.

pip install -r requirements.txt      # fastmcp — the engine itself stays stdlib
python3 server.py                    # stdio entrypoint your MCP client launches

Register it with any MCP-compatible client:

{
  "mcpServers": {
    "media-engine": {
      "command": "python3",
      "args": ["/absolute/path/to/media-engine/server.py"]
    }
  }
}

Tool results return the output path, the ffmpeg command(s) run, and pass counts; failures come back as {"ok": false, "error": ...} with the ffmpeg stderr tail.

Web UI

webserver.py serves a single-page drag-and-drop UI over stdlib http.server — no frameworks, no build step, no dependencies. The whole frontend is one self-contained web/index.html (vanilla JS, inline styles, embedded font and icons).

python3 webserver.py                      # http://127.0.0.1:8765
python3 webserver.py --host 0.0.0.0 --port 9000

Drop in a file, pick an operation (deep-linkable as /#video_to_gif), tweak the auto-generated parameter form, run. The op executes in a background thread inside a temp workdir, progress streams live over Server-Sent Events, and the result previews in-browser (image/video) with a download button. Abandoned jobs are reaped after 30 minutes.

The HTTP API underneath is plain and scriptable:

Route Method Purpose
/ GET the UI (web/index.html)
/api/operations GET full registry as JSON (params, types, ranges, defaults)
/api/run/<op> POST multipart upload (file field __input__ + param fields) → {job_id}
/api/progress/<job_id> GET live progress as an SSE stream
/api/result/<job_id> GET download the output (one-shot; workdir cleaned after)
/health GET liveness check

Security note: the server runs ffmpeg on uploaded input and binds localhost by default. Don't expose it to an untrusted network.

Operations

19 operations — 10 video, 5 audio, 4 image. Full parameter details for any op: python3 cli.py info <op> (or /api/operations, or the MCP tool schemas — same data).

Video

Operation Description Params
video_to_gif Video to animated GIF (two-pass palette) or WebP gifFpsPreset gifResolutionPreset gifExportWebp
video_speed Speed up / slow down video + audio in sync (atempo-chained) speed (−100…100 %)
add_watermark Overlay a scaled, alpha-blended image watermark coverImage watermarkPosition watermarkSize watermarkOpacity watermarkX watermarkY
convert_video Change container format, codec, and bitrate videoFormat videoCodec videoBitrate
resize_video Fit into a preset resolution/orientation frame with letterbox padding resolutionPreset resizeVideoFormat
trim_video Cut a segment starting at startTime for duration (re-encodes) startTime duration
extract_audio Extract the audio track (optionally transcoding), or strip audio entirely extractAudioAction extractAudioFormat extractAudioBitrate
rotate_video Rotate 90° CW/CCW, 180°, or mirror horizontally rotation
grayscale_video Named color-grade preset plus manual brightness/contrast/saturation trim styleMode styleIntensity brightnessAdjust contrastAdjust saturationAdjust
burn_subtitles Hardcode SRT/VTT/ASS subtitles into the video frame (irreversible) subtitles

Audio

Operation Description Params
convert_audio Transcode to a target format with optional loudness normalize / volume boost audioFormat normalizeLoudness volumeBoostPercent outputSampleRate bitrate
trim_audio Cut a segment from an audio file (start + duration) startTime duration
audio_settings Channel-layout change, full reverse, and/or fade in/out in one pass audioChannelMode reverseAudioEnabled fadeInDuration fadeOutDuration
add_metadata Write/remove ID3-style text metadata and embed/strip cover art, per container support coverImage metadataArtist metadataTitle metadataReleaseDate metadataGenre removeCover removeAllMetadata
audio_speed Change audio tempo (atempo-chained; same percent scale as video_speed) speed (−100…100 %)

Image

Operation Description Params
resize_image Resize/reformat a still image to a target aspect ratio and size imageAspectRatio imageResizePreset imageCustomWidth imageCustomHeight imageOutputFormat
rotate_image Rotate 90°/180° and/or flip horizontally/vertically rotation flipHorizontal flipVertical
grayscale_image Black-and-white via intensity-blended color mixer plus preset contrast/brightness blackWhiteMode blackWhiteIntensity blackWhiteContrast blackWhiteBrightness
sharpness_image Box blur and/or unsharp-mask sharpening sharpenAmount blurStrength

Architecture

engine/
  opmodel.py      the model: Param (typed, validated) · Context · Operation
  ops/
    video.py      10 video operations
    audio.py       5 audio operations
    image.py       4 image operations
  operations.py   registry aggregator + run_operation (validate → probe → build → run)
  ffmpeg.py       process layer: binary resolution, -progress pipe:1 parsing,
                  bounded stderr tails, version capability probe, per-OS null device
  probe.py        ffprobe → MediaInfo (dimensions, duration, bitrates, audio presence)
cli.py            CLI face
server.py         MCP face (one typed tool per op, generated from the registry)
webserver.py      web face (stdlib http.server + SSE job streaming)
web/index.html    the entire frontend — one self-contained page, no build step
recon/            upstream recipe inventory + porting assessment

An operation is data plus one function:

Operation(
    id="video_to_gif", category="video",
    description="Video to animated GIF (two-pass palette) or WebP.",
    params=[Param("gifFpsPreset", "choice", 12, choices=[8, 10, 12, 15, 24, 25, 30, 50, 60]), ...],
    build=_build_gif,                 # Context -> [Pass, ...] (the ffmpeg invocations)
    output_ext=lambda p: "webp" if p.get("gifExportWebp") else "gif",
    needs_probe=False,                # True = run ffprobe first, results in ctx.info
)

run_operation validates params against the declared specs, probes the input if the op asks for it, calls build to get one or more ffmpeg Passes (multi-pass recipes like palette-gen GIF just return two), and executes them with live progress. Temp files created via ctx.tempfile() are cleaned up automatically.

To add an operation: write a _build_<op>(ctx) function and append an Operation(...) entry in the right engine/ops/*.py module. That's the whole job — the CLI subcommand, the typed MCP tool, and the web form all materialize from the entry.

Portability

  • ffmpeg version probe — -fps_mode passthrough only exists on ffmpeg ≥ 5.0; engine/ffmpeg.py parses ffmpeg -version once and falls back to the legacy -vsync passthrough on older builds (verified end-to-end on 4.4.2; the modern-flag path is the same one the upstream recipes used against ffmpeg 6.x).
  • Per-OS null device — /dev/null vs NUL chosen at runtime.
  • Binary resolution — PATH lookup with FFMPEG_BIN / FFPROBE_BIN env overrides; no .exe assumptions.
  • Machine-readable progress — -progress pipe:1 key-value stream instead of parsing locale/version-dependent stderr time= lines.
  • Python — 3.9+, stdlib only (dataclasses, argparse, http.server, subprocess). The MCP face additionally follows fastmcp's Python requirement.

Provenance

The recipes were ported and adapted from media-by-outlaw2082 (GPL-3.0), an Electron GUI over ffmpeg — credit to Outlaw2082 for the original operation set and command recipes. This project reimplements them as a headless, dependency-free engine, keeping the recipes' behavior while fixing a few issues found during the port:

  • rotate_video "Mirror" was exposed upstream but had no filter mapping (silent no-op); it now emits hflip.
  • trim_audio / audio_settings relied on ffmpeg's default-encoder-by-extension guess; the codec is now set explicitly.
  • audio_speed labeled its input "percent" but clamped a raw 0.5–2.0 factor; it now uses the same percent scale as video_speed with an atempo chain (no 2× ceiling).
  • sharpness_image mapped its slider to unsharp amounts up to 20, outside ffmpeg's valid [-2, 5] range (errored on every build); remapped to 0–5.

The recon/ directory documents the original operation inventory and the porting assessment.

License

GPL-3.0 — inherited from the upstream project the recipes derive from, and kept gladly.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选