mpv-bindgen-sys: Complete libmpv bindings, raw + curated, generated by hs-bindgen

[ bsd3, library, media ] [ Propose Tags ] [ Report a vulnerability ]

Machine-generated low-level bindings to the whole libmpv client API (client, render, OpenGL render, and stream callback headers): the raw hs-bindgen output (Mpv.Sys.Bindgen.*) plus a curated Mpv.Sys layer with per-function safe/unsafe FFI decisions and native Haskell scalars. libmpv's own header documentation rides along in the haddocks.

A generated translation unit of C _Static_asserts checks the baked layouts against your installed libmpv at every build, so ABI divergence is a compile error rather than memory corruption.

Supports 64-bit Linux, macOS, and Windows, with libmpv client API >= 2.0 development headers present. See the README for a quick start, the licensing note (libmpv itself is GPLv2+ by default), and the list of deliberate omissions.


[Skip to Readme]

Flags

Manual Flags

NameDescriptionDefault
abi-assertions

Verify the baked ABI layout against the system libmpv headers at build time via generated C static assertions (disable only to diagnose)

Enabled
abi-assertions-exact

Assert every sizeof exactly, including any union-member structs the default lets libmpv append to. For maintainers checking a newer libmpv; consumers should leave it off. Only has an effect together with abi-assertions.

Disabled
optimize

Build with -O2. Package ghc-options win over cabal's optimization setting, so use -f -optimize for faster iterative builds of this large package.

Enabled
optimize-aggressively

Build with expensive optimizations. Consider this the release build: slow, but paid once if caching is set up, so it may be worth it for artifacts you intend to ship.

Currently: -O2 -fexpose-all-unfoldings -flate-specialise -flate-dmd-anal -fstg-lift-lams.

Disabled
strict

EXPERIMENTAL: compile the generated modules with Strict.

The emitted hs-bindgen code has not been audited for laziness dependence; main library only.

NB: Implies StrictData (GHC semantics), so -f -strict-data does not undo strict fields.

Disabled
strict-data

Compile the generated modules with StrictData (strict fields on every generated record). Main library only; the vendored runtime keeps its upstream semantics. Disable if you need lazy fields.

Enabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.0.0.1
Change log CHANGELOG.md
Dependencies base (>=4.17 && <4.23), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.9), fin (>=0.3.2 && <0.4), mpv-bindgen-sys, primitive (>=0.9 && <0.10), record-hasfield (>=1.0 && <2), some (>=1.0.6 && <1.1), template-haskell (>=2.19 && <2.25), vec (>=0.5 && <0.6), vector (>=0.13 && <0.14) [details]
Tested with ghc >=9.8 && <9.9 || >=9.10 && <9.11 || >=9.12 && <9.13 || >=9.14 && <9.15
License BSD-3-Clause[multiple license files]
Copyright (c) 2026 Jeremy Nuttall
Author Jeremy Nuttall
Maintainer jeremy@jeremy-nuttall.com
Uploaded by jtnuttall at 2026-10-11T01:22:17Z
Category Media
Home page https://github.com/jtnuttall/lithon#readme
Bug tracker https://github.com/jtnuttall/lithon/issues
Source repo head: git clone https://github.com/jtnuttall/lithon(mpv-bindgen-sys)
Distributions
Downloads 3 total (3 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for mpv-bindgen-sys-0.0.0.1

[back to package description]

mpv-bindgen-sys

Fully automated, luxury low-level Haskell bindings to libmpv, the library behind the mpv media player: the whole client API, machine-generated from its headers using an ever-so-slightly hacked version of hs-bindgen.

This package gets you libmpv in full — playback driven through options, commands, properties, and events; the render API for drawing video into your own OpenGL context or a memory buffer; and custom stream protocols backed by your own callbacks.

This package aims to support 64-bit Linux, macOS, and Windows.

This library aims to be:

  • Complete by construction: Generated from all four libmpv client API headers (client.h, render.h, render_gl.h, stream_cb.h), so a gap is either a code generation bug or a deliberate omission rather than a binding waiting to be hand-written.
  • Curated: The Mpv.Sys.* layer wraps hs-bindgen's output with best-effort Haskell casing, native scalar types, and FFI safety decisions and recommendations.
  • First-class mpv docs: Every binding carries libmpv's header documentation, and notes from the code generator's curation layer.
  • ABI-verified: A generated translation unit of C _Static_asserts verifies the library's baked layouts against your libmpv at every build. Divergence is a compile error naming the declaration, not memory corruption. See ABI verification for more.

Most of what you do with libmpv goes through options, commands, and properties, which the mpv manual documents rather than the headers.

Quick start

Install build requirements

Install libmpv development files for client API >= 2.0 (mpv 0.35 or newer) where pkg-config can find them as mpv. The development headers must be present, not just the shared library. pkg-config --modversion mpv prints the client API version (e.g. 2.5.0), not the mpv release.

Common setups:

  • Debian/Ubuntu: apt install libmpv-dev
  • macOS: brew install mpv
  • Arch: pacman -S mpv
  • Fedora: dnf install mpv-devel
  • Windows: WSL2 with the Linux instructions, or native MSYS2. See Windows set up

Set up your project

  1. Add mpv-bindgen-sys to build-depends
  2. Replace the contents of Main.hs with:
{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE OverloadedRecordDot #-}

import Control.Monad (unless, when)
import Foreign.C.ConstPtr (ConstPtr (..))
import Foreign.C.String (peekCString, withCString)
import Foreign.Ptr (nullPtr)
import Foreign.Storable (peek)
import Mpv.Sys.Client qualified as Mpv

main :: IO ()
main = do
  mpv <- Mpv.create
  when (mpv == nullPtr) (fail "mpv_create failed")
  check "vo" =<< withCString "vo" \name -> withCString "null" \value ->
    Mpv.setOptionString mpv (ConstPtr name) (ConstPtr value)
  check "initialize" =<< Mpv.initializeSafe mpv
  check "loadfile" =<< withCString "loadfile video.mkv" \cmd ->
    Mpv.commandString mpv (ConstPtr cmd)
  let loop = do
        event <- Mpv.waitEventSafe mpv (-1)
        eventId <- peek event.event_id
        case eventId of
          Mpv.MPV_EVENT_END_FILE -> pure ()
          Mpv.MPV_EVENT_SHUTDOWN -> pure ()
          _ -> loop
  loop
  Mpv.terminateDestroySafe mpv
 where
  check what rc = unless (rc >= 0) do
    msg <- peekCString . unConstPtr =<< Mpv.errorString rc
    fail (what <> ": " <> msg)

This plays video.mkv with the video output disabled (audio still plays) until the file ends. initialize, waitEvent, and terminateDestroy can block for a long time, so the example uses their Safe flavor; see Safe and unsafe FFI.

For a fuller example, see mpv-headless in lithon-examples.

Windows set up

There are two ways to set this up that I am aware of. In order of convenience:

WSL2

The Linux instructions apply unchanged.

Native (MSYS2)

Install build dependencies:

pacman -Syyu # repeat/restart terminal if pacman asks you to
pacman -S mingw-w64-ucrt-x86_64-mpv mingw-w64-ucrt-x86_64-pkgconf

[!NOTE] The UCRT64 pkgconf is required. MSYS2 pkg-config reports POSIX-style paths that GHC can't use on Windows.

Stack users

[!IMPORTANT] Stack users need some additional setup.

Adjust the library version in extra-deps to your desired target.

  1. Run the pacman commands above through stack exec -- pacman ... so that the packages are installed in stack's MSYS2.
  2. Add msys-environment: UCRT64 to your stack.yaml.
  3. Add mpv-bindgen-sys-0.0.0.1 to extra-deps in your stack.yaml. Running stack build should print out a helpful, pasteable entry for this purpose.

Your stack.yaml should look something like this:

snapshot: lts-24.51
packages:
  - .
extra-deps:
  - mpv-bindgen-sys-0.0.0.1 # hash may be here if you copy from stack build
msys-environment: UCRT64 # important: build will not work without this
Direct cabal build using an MSYS2 Bash session (e.g., Git Bash)

You can point cabal at the UCRT64 toolchain and your ghcup GHC:

export PKG_CONFIG_PATH="/c/msys64/ucrt64/lib/pkgconfig"
export PATH="/c/msys64/ucrt64/bin:$PATH"
cabal build \
  --with-compiler=/c/ghcup/ghc/9.12.2/bin/ghc.exe \
  --extra-lib-dirs=/c/msys64/ucrt64/lib \
  --extra-include-dirs=/c/msys64/ucrt64/include

Adjust the GHC path to match your install. Keep ucrt64/bin on PATH when running the program, so Windows finds the libmpv DLL.

Library structure

For most uses, you will import Mpv.Sys qualified as Mpv. Mpv.Sys re-exports one module per libmpv header:

Module Header Contents
Mpv.Sys.Client client.h Core client API: handles, options, commands, properties, events.
Mpv.Sys.Render render.h Render API: drive video output from your own rendering loop.
Mpv.Sys.RenderGl render_gl.h OpenGL backend parameters for the render API.
Mpv.Sys.StreamCb stream_cb.h Custom stream protocols via user callbacks.

The raw hs-bindgen output lives underneath as Mpv.Sys.Bindgen.*, if you need to drop down to C types: each family (Mpv.Sys.Bindgen.Client and so on) carries the types and constants, with the foreign imports in its .Safe and .Unsafe modules and each function's address in .FunPtr.

Safe and unsafe FFI

Most functions come in both FFI flavors: command is an unsafe foreign import; commandSafe is the safe one.

General rules for safe vs. unsafe FFI:

  • You must use a safe call if C will call back into Haskell.
  • You should use a safe call if the C call could take a while (e.g., waiting on some OS event, or a locking mechanism, etc.).
  • You should use an unsafe call if the C call is fast; unsafe calls block the current GHC thread (capability) and the garbage collector, but their overhead is very low compared to safe calls.

libmpv makes the first rule bite more often than most C libraries. It runs your callbacks (the wakeup callback, the render update callback, custom stream callbacks) on its own threads, and sometimes on yours: client.h warns that the wakeup callback can be called from a thread while an mpv API function is running. If any callback you hand to mpv is a Haskell function, use the Safe aliases for everything except the unsafe-only functions below.

The curated registry settles the rest:

  • Safe only, because a callback fires during the call: setWakeupCallbackSafe and renderContextSetUpdateCallbackSafe invoke the new callback once immediately, and renderContextCreateSafe calls the get_proc_address you pass in mpv_opengl_init_params. That last one is curated by hand: the callback travels inside the mpv_render_param array, where the generator's callback census cannot see it.
  • Both flavors, but use the Safe one: waitEventSafe (up to its timeout; forever when negative), waitAsyncRequestsSafe, initializeSafe, destroySafe, and terminateDestroySafe can block for a long time (an unsafe call would stall the garbage collector program-wide meanwhile).
  • Unsafe only: clientId, clientName, errorString, eventName, eventToNode, free, freeNodeContents, getTimeNs, and getTimeUs cannot block or call back.
  • Every other function exports both. Many of them wait for the playback core, which client.h says "can take an unbounded time", and several run the wakeup callback synchronously.

For the safe-only functions, the genuine unsafe import stays reachable under the family's Mpv.Sys.Bindgen.*.Unsafe module, if you know better.

Conversion to and from C types

Mpv.Sys re-exports Mpv.Sys.Runtime, the conversion vocabulary you'll actually reach for: the CEnum classes for moving between enum newtypes (Mpv_event_id, Mpv_format, …) and their integral representations.

C scalars in parameters and results arrive as their native Haskell twins (int as Int32, double as Double); struct fields and pointees keep their C types.

Platform support

64-bit platforms only. Linux, macOS (Homebrew mpv), and Windows (MSYS2 UCRT64, including the LLP64 layouts) are all targets. 32-bit targets are rejected by the ABI assertions — 64-bit layouts are baked in.

Common issues

  • create returns nullPtr — libmpv refuses to start unless the LC_NUMERIC locale category is "C" (see "Basic environment requirements" in client.h). If your program or a GUI toolkit calls setlocale(LC_ALL, ""), reset LC_NUMERIC to "C" before create.
  • foo or fooSafe? — every function's haddock states its flavor choice, and the rationale, under its mpv-bindgen-sys notes section. If you registered a Haskell callback, read Safe and unsafe FFI first.
  • Calling mpv from a callback — don't. The headers forbid calling the API from the wakeup and update callbacks, and a stream callback that does can deadlock. Have the callback signal a thread (e.g. with tryPutMVar) that does the work.
  • The render API and OpenGL — with the OpenGL backend, the renderContext* calls need your OpenGL context current on the calling OS thread (render.h). Make those calls from a bound thread (main, or one started with forkOS): an unbound Haskell thread can move between OS threads from one call to the next.
  • free is ambiguous — mpv_free is bound as free, which clashes with Foreign.Marshal.Alloc.free. Import Mpv.Sys qualified.

What is not bound

  • mpv_client_api_version: it returns unsigned long, whose width differs between LP64 and LLP64, and hs-bindgen bakes the raw foreign imports' FFI types from the generation host. That is the reason sdl3-bindgen-sys omits SDL's seven long-typed functions, too. The MPV_CLIENT_API_VERSION constant remains, but it holds the version these bindings were generated from (2.5), baked at generation time, not the version of the libmpv you link. For the running player, read the mpv-version property.
  • The MPV_CPLUGIN_DYNAMIC_SYM symbol table: with that macro defined, the headers turn every mpv_* function into a pfn_mpv_* function pointer for C plugins loaded into the mpv player. These bindings are generated without it and bind the functions directly.
  • MPV_RENDER_PARAM_DRM_OSD_SIZE: a #define alias of an enum constant, which hs-bindgen does not bind. Use MPV_RENDER_PARAM_DRM_DRAW_SURFACE_SIZE, the value it names.

The complete list, checked at every generation, is the skip ledger: every declaration hs-bindgen skips, why, and what lithon does about it.

Deprecated declarations are bound, not omitted: MPV_ENABLE_DEPRECATED keeps its default, so MPV_EVENT_IDLE, MPV_EVENT_TICK, and mpv_get_wakeup_pipe are present.

libmpv versions

libmpv client API >= 2.0 (mpv 0.35) is required; the surface is generated from client API 2.5 (mpv 0.41.0).

Two functions are newer than that floor. They still compile and link against an older libmpv: their wrapper C is gated on MPV_CLIENT_API_VERSION, and below the gate:

  • delProperty (mpv_del_property, client API 2.1 / mpv 0.36) returns MPV_ERROR_UNSUPPORTED without calling libmpv.
  • getTimeNs (mpv_get_time_ns, client API 2.2 / mpv 0.37) returns mpv_get_time_us times 1000: the same clock, at microsecond resolution.

Below the gate, their raw Mpv.Sys.Bindgen.Client.FunPtr addresses are nullFunPtr.

No struct layout or enum value changed between client API 2.0 and 2.5, so the ABI assertions check the same layouts on every supported libmpv. MPV_RENDER_PARAM_AMBIENT_LIGHT, deprecated in 2.5, is still defined and still bound.

Versioning

The 0.0.x series is experimental: pin to the minor (e.g., >=0.0.0.1 && <0.0.1) and expect surface-shaping changes.

ABI verification

The cabal flag abi-assertions (default) turns on ABI verification, which guards against unexpected layout divergence between libmpv versions and varying operating systems.

When ABI assertions are on, cbits/abi_assertions.c statically checks every size, alignment, field offset, and enum value the Haskell side expects against your libmpv headers.

Maintainers can build with -f abi-assertions-exact to assert every sizeof exactly, including structs the default checks only as a prefix (libmpv has none today). Consumers should leave it off.

What to do when you get "static assertion failed"

  1. Check your libmpv: pkg-config --modversion mpv. Client API >= 2.0 is required
  2. Make sure you are on a supported architecture. 32-bit targets are not presently supported (see Platform support).
  3. Report it at the issue tracker with the failing lines, your libmpv version, and your platform.
  4. If you are comfortable doing so, open a PR regenerating the bindings from the newer libmpv. The lithon-codegen README describes the pipeline.

Building with -f-abi-assertions turns off the check, not the mismatch: the bindings would then read and write the baked layout against headers that disagree with it.

Known documentation issues

  • libmpv documents some struct members and enum constants with a comment after the declaration (char *string; /** valid if … */ in mpv_node, the /// level names in mpv_log_level). In the generated haddocks those comments attach to the following member; read them against the header.
  • The raw layer keeps hs-bindgen's default type names, the C spelling capitalized: Mpv_node, Mpv_event_id, Mpv_render_param.
  • enum mpv_render_update_flag is bound under its typedef name, Mpv_render_context_flag; mpv_node's anonymous union is Mpv_node_u; struct _drmModeAtomicReq is the opaque C_DrmModeAtomicReq.
  • Each header's opening overview comment lands in its first declaration's haddock (client.h → mPV_MAKE_VERSION, render.h → Mpv_render_context, render_gl.h → Mpv_opengl_init_params, stream_cb.h → Mpv_stream_cb_read_fn).

Provenance and licensing

Generated by hs-bindgen driven by the repository's lithon-codegen, from the libmpv client API 2.5 headers (mpv 0.41.0). The generated tree is never hand-edited, but bugs are mine, not mpv's or hs-bindgen's: report them at the issue tracker.

  • mpv-bindgen-sys is BSD-3-Clause (see LICENSE).
  • The libmpv client API headers, and the header documentation embedded in the haddocks, are ISC-licensed (LICENSE_libmpv).
  • The vendored hs-bindgen and c-expr runtimes are BSD-3-Clause, (c) Well-Typed LLP and Anduril Industries (LICENSE_hs-bindgen-runtime, LICENSE_c-expr-runtime).

libmpv's license applies to your program

The ISC license covers the headers, not libmpv. libmpv is GPLv2 or later by default, and LGPLv2.1 or later only when mpv is built with -Dgpl=false (see mpv's Copyright file). A program that links libmpv is subject to libmpv's license, whatever the license of these bindings.

In practice, shipping a Haskell program built on this package means shipping libmpv with it, statically linked or bundled, so plan for the GPL's obligations (or use an LGPL build of libmpv, linked dynamically).

This package only declares the pkg-config dependency; how libmpv is linked is up to your build. For a static link, use the flags pkg-config --static --libs mpv reports.