FFMpegCore builds FFmpeg command lines from typed C# builders
A .NET FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your C# applications
At a glance
- What is it?
- A .NET wrapper that turns ffmpeg and ffprobe invocations into fluent C# builders, pipes frames straight from memory, and ships helpers for routine edits. The documented surface covers arguments and binaries in depth, and stops short of process control and error handling.
- Who is it for?
- FFMpegCore fits a C# service that converts uploaded media on Windows, macOS or Linux and wants typed arguments instead of hand quoted command lines. The fluent builder, the three pipe sources and the helper methods cover most routine work.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Every call spawns the ffmpeg or ffprobe binary
FFMpegCore is a .NET Standard wrapper around two programs that already exist on most machines that handle media: ffmpeg for conversion and ffprobe for analysis. It bundles no codec of its own. Each call launches one of those binaries as a child process and reads what comes back, so deploying an application that uses it is as much a packaging exercise as a code exercise, since the binaries have to exist and be reachable at run time.
Analysis takes two lines, and those two lines define the shape of the whole API:
var mediaInfo = await FFProbe.AnalyseAsync(inputPath);`FFProbe.AnalyseAsync` and its blocking twin `FFProbe.Analyse` take a path and bind the result to mediaInfo. Past that point the shape of the returned object is not described anywhere in the repository, so you cannot learn from the documentation which member holds duration, which holds bitrate, or how the stream list is exposed. You learn it from the compiler.
The audience follows from that design. Teams already writing C# and already shipping ffmpeg on their build agents or containers get the most out of the package, because the saving is real but narrow: no hand quoted argument strings, no shell escaping, and every call stays inside the type system. Teams working in another language have a NuGet package they cannot reference, and teams with a working process layer around ffmpeg already have nothing left to adopt here.
The fluent builder maps onto real ffmpeg flags
Conversion is a chain of builder calls, one per flag group, ending in a process call that blocks or does not. The longest example in the repository is also the most informative, since it names every setting a typical web transcode needs:
FFMpegArguments
.FromFileInput(inputPath)
.OutputToFile(outputPath, false, options => options
.WithVideoCodec(VideoCodec.LibX264)
.WithConstantRateFactor(21)
.WithAudioCodec(AudioCodec.Aac)
.WithVariableBitrate(4)
.WithVideoFilters(filterOptions => filterOptions
.Scale(VideoSize.Hd))
.WithFastStart())
.ProcessSynchronously();Reading the chain tells you what each setting reaches. `WithVideoCodec(VideoCodec.LibX264)` names the encoder, the constant rate factor is 21, `WithAudioCodec(AudioCodec.Aac)` names the audio codec, and `Scale(VideoSize.Hd)` inside `WithVideoFilters` is the 720p scale named in the comment above the snippet. `WithFastStart()` is the faststart that same comment points at for web playback. Nothing in the chain mentions temporary files or a work directory, so the media travels as a path in and a path out.
One detail is easy to skim past and worth checking before you depend on it: the boolean sitting in front of the options callback is never explained. The call reads `OutputToFile(outputPath, false, options => options` and no page in the repository says what that argument controls, whether overwrite, a prompt, or something else. A second detail sits two examples further down, where the codec is given as the literal `"vp9"` rather than an enum member, so the builder takes text as well as types and passes it on.
Pipe sources remove the temporary file round trip
Converting from disk is the easy case. Converting from memory is what `IPipeSource` exists for: instead of writing frames to jpeg or png and handing a path to ffmpeg, you hand the process a stream, a raw video sequence, or raw audio samples while your code is still producing them. The interface has three implementations, `StreamPipeSource`, `RawVideoPipeSource` and `RawAudioPipeSource`.
The sink side mirrors the source side:
await FFMpegArguments
.FromPipeInput(new StreamPipeSource(inputStream))
.OutputToPipe(new StreamPipeSink(outputStream), options => options
.WithVideoCodec("vp9")
.ForceFormat("webm"))
.ProcessAsynchronously();`StreamPipeSink` wraps your output stream, `ForceFormat("webm")` sets the container, and the codec arrives as text rather than an enum member, which confirms that the fluent layer is a typed convenience with a pass through.
Generated frames take the same route through a wrapper you write once:
var videoFramesSource = new RawVideoPipeSource(CreateFrames(64))
{
FrameRate = 30 //set source frame rate
};
await FFMpegArguments
.FromPipeInput(videoFramesSource)
.OutputToFile(outputPath, false, options => options
.WithVideoCodec(VideoCodec.LibVpx))
.ProcessAsynchronously();Note where the frame rate lives: on the source, not on the output options, which is a different arrangement from the file based chain where encoder, filters and container all hang off the output. Both image packages, `FFMpegCore.Extensions.System.Drawing.Common` and `FFMpegCore.Extensions.SkiaSharp`, provide a `BitmapVideoFrameWrapper` that adapts a `System.Drawing.Bitmap` or an `SKBitmap` to `IVideoFrame`, so a bitmap generating library plugs in without a custom pipe source.
The binaries must exist before the first call
Nothing here substitutes for ffmpeg itself, so binary acquisition is the first real decision a new user makes. The automated route is a separate package, `FFMpegCore.Extensions.Downloader`, which installs ffmpeg and ffprobe at run time into the folder you configure:
GlobalFFOptions.Configure(options => options.BinaryFolder = "./bin");
await FFMpegDownloader.DownloadBinaries();Where those binaries come from is the part worth knowing. The download path is not ffmpeg.org. It is the api behind ffbinaries, a third party service, which matters if you care about what a build agent reaches out to at run time.
The manual route is documented per platform, with plain package manager commands:
choco install ffmpeg -y
brew install ffmpeg
sudo apt-get install -y ffmpegOn Windows the binaries end up in `C:\ProgramData\chocolatey\lib\ffmpeg\tools\ffmpeg\bin`, on Macs under `/opt/homebrew/bin` for Apple Silicon or `/usr/local/bin` for Intel, and on Ubuntu in `/usr/bin`. Whichever route you take, lookup starts as PATH: the configured value begins as an empty string and expects ffmpeg to be found that way until you overwrite it.
A close read catches one more thing. The path configuration example stops in the middle of an expression, right after `TemporaryFilesFolder = "/tmp"`, with no closing brace and no second option listed, so the configuration surface actually shown in the README amounts to two keys on `FFOptions`: `BinaryFolder` and `TemporaryFilesFolder`. That fragment also mixes a Unix path into a section that had just described Windows, so treat the snippet as an illustration rather than a specification.
The asynchronous promise covers less than it reads
The summary line advertises a library that supports both synchronous and asynchronous calls, and at the builder level that holds: `ProcessSynchronously` and `ProcessAsynchronously` are both documented, and analysis has both forms. Lower down, the helper methods are almost entirely blocking. Snapshot, join, sub video, mute, extract audio, replace audio and poster with audio all appear in synchronous form only. The one exception in the documented set is the GIF helper, the only place where a helper and its `Async` twin stand next to each other:
await FFMpeg.GifSnapshotAsync(inputPath, outputPath, new Size(480, -1), TimeSpan.FromSeconds(10));That example also carries a second behaviour worth knowing. Supplying -1 to either dimension of the size hands the resize to ffmpeg so the aspect ratio survives, which is why `new Size(480, -1)` is the documented way to build a GIF from a source without hard coding a frame shape, while the snapshot examples above it use a fixed `new Size(200, 400)`.
The trap is reading the helpers as a complete asynchronous surface. Assume a helper blocks until you have checked its own signature, because the headline sentence describes the builder, not the convenience layer. The same split shows up in snapshot handling: one call writes the image to disk, while the in memory form lives behind the image extension packages as `FFMpegImage.Snapshot`.
Trim, join and audio helpers wrap routine edits
For common edits the package skips the builder entirely and offers named methods. Cutting a segment is a pair of `TimeSpan` values:
FFMpeg.SubVideo(inputPath,
outputPath,
TimeSpan.FromSeconds(0),
TimeSpan.FromSeconds(30)
);Joining parts is one call with the destination first and every part after it, and joining a numbered image sequence takes a frame rate, so `FFMpeg.JoinImageSequence` turns a run of still frames into a playable file without touching the fluent chain. Mute, `FFMpeg.ExtractAudio`, `FFMpeg.ReplaceAudio` and `FFMpeg.PosterWithAudio` cover the track manipulations that show up in nearly every upload pipeline, and the image packages add a second route through `Image.FromFile` followed by `AddAudio`.
Each helper is a thin wrapper over the same builder, so the ceiling is the builder's ceiling. Anything the fluent chain cannot express, these methods cannot either, and the repository directs you to the `FFMpegCore.Arguments` namespace for the argument classes behind them. That pointer is a namespace rather than a guide, so the full argument list is discovered through the type system rather than read off a table.
Spawning the binaries yourself is the real alternative
There is no competing wrapper to weigh against this one, because the alternative is not another library. It is the command line itself, and the project documents where to get the binaries precisely because they stand alone. Starting ffmpeg and ffprobe from your own process handling code reaches the same results with the exit code, standard error and cancellation token in your own hands.
The difference in approach is where the decision sits. The wrapper wins on argument construction: enums for codecs and sizes, one lambda per output, named methods for the boring edits, and no quoting mistakes when a path contains a space. Hand written arguments win on process control: timeouts, cancellation, retry after a transient failure, capturing standard error into your logs, and streaming progress back to a caller. None of those appear in the documented surface here, and nothing states what the analysis call hands back when ffprobe exits with a failure, so error handling stays yours in both routes.
Choose the wrapper when conversion is a solved, repeatable step inside a C# service. Choose raw process calls when the binary is one stage of a pipeline you already supervise, and your monitoring and retry logic already speaks process handles.
The tree shows the package split and one odd file
The solution is split the way a consumer would expect: a core `FFMpegCore/` project, three extension packages for downloads, `System.Drawing.Common` and `SkiaSharp` support, a `FFMpegCore.Test/` project, and a `FFMpegCore.Examples/` project that lines up with every snippet here, all wired through `FFMpegCore.sln` and a shared `Directory.Build.props`. Test environment definitions sit at the root in `testenvironments.json`, which is where to look first for how the project provisions ffmpeg during its own test runs.
One root entry does not belong to a media library at all: `CLAUDE.md`, an instruction file for coding agents, sits beside the solution and the build props. The continuous integration badge points at `ci.yml` on the default branch `main`. Together the tree describes a project with a real test setup and a build that still runs, wrapped around documentation that lives almost entirely in the README.
On upkeep and adoption cost, the record is thin. The last push was on 2026-09-23, the newest recorded tag is v5.5.0 from 2026-09-20, and the tags before it stop at v1.0.11 dated 2019-09-30, so the version numbers jump rather than climb one at a time. Permission is simple to carry into a product, since the package is published under the MIT licence and that is the whole grant you get, with no copyleft obligation to design around.
Editorial conclusion
FFMpegCore fits a C# service that converts uploaded media on Windows, macOS or Linux and wants typed arguments instead of hand quoted command lines. The fluent builder, the three pipe sources and the helper methods cover most routine work. Walk away when you need cancellation, timeouts or progress from the running process, since none of that is documented here. Verify first that `BinaryFolder` resolves on every build agent and that the binaries already installed there match the build the downloader fetches from the ffbinaries API.
Frequently asked questions
Does FFMpegCore include FFmpeg itself, or do I have to install the binaries?
FFMpegCore wraps the external programs and carries no binary of its own. You install ffmpeg and ffprobe with a package manager, point `BinaryFolder` at them, or let the `FFMpegCore.Extensions.Downloader` package fetch both at run time using the api behind ffbinaries.
How do I run an FFMpegCore conversion asynchronously?
Every builder chain ends in a process call, and both `ProcessSynchronously` and `ProcessAsynchronously` are documented. Among the helper methods, only the GIF snapshot helper is shown in an `Async` form, so check the signature of the specific helper you intend to call.
Can FFMpegCore encode video straight from memory instead of a file on disk?
Yes. `IPipeSource` has three implementations, `StreamPipeSource`, `RawVideoPipeSource` and `RawAudioPipeSource`, so streams, frame sequences and audio samples can feed the process directly. Both image extension packages also supply a `BitmapVideoFrameWrapper` that adapts a bitmap to `IVideoFrame`.
Which FFMpegCore version should I target?
The newest recorded tag is 5.5.0, dated 2026-09-20. The tags before it stop at 1.0.11 from 2019-09-30, so the numbering jumps rather than climbing one step at a time.
How do I pass an argument the FFMpegCore helper methods do not expose?
Move to the fluent builder, or to the argument classes in the `FFMpegCore.Arguments` namespace that the repository names as the home of the available arguments. The builder takes a codec either as an enum member or as a plain string, so text reaches the command line instead of being validated.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rosenbjerg-ffmpegcore)Was this useful?