Video encoding, muxing and analysis — a GUI over FFmpeg, FFprobe and av1an.
Portable and self-contained on Windows, Linux and macOS. Nothing to install.
Download · Features · What's new since the fork · What a release bundles · Building
Nmkoder wraps the tools you would otherwise drive by hand. Point it at a file and it reads every stream out of it; from there you can re-encode, remux, retime, deinterlace, tone-map, cut, measure or batch the lot, and watch what it is doing in a log you can read.
This is a fork of n00mkrad/nmkoder, which was WinForms on .NET Framework and Windows-only. The UI has been rebuilt, the app ported to three platforms, and a fair amount added on top — see What's new since the fork.
| Tab | What it is for |
|---|---|
| Quick Convert | One encode, built for you: H.264, H.265, VP9 and AV1 through the same encoder binaries the AV1AN tab drives, GIF and image sequences, per-track audio, subtitle burn-in, loudness normalization. |
| AV1AN | Chunked, parallel encoding through av1an, with target-quality modes (VMAF, SSIMULACRA2, Butteraugli, XPSNR) and a full per-encoder argument grid. |
| Utilities | The jobs beside the encode: a CRF ladder that samples the source and reports what each CRF would cost, quality metrics, bitrates, colour metadata, grain tables, deinterlaced exports, lossless cuts, concatenation, bitrate charts, subtitle OCR. |
Portable archives for all four platforms are attached to every release. Unpack and run — each build is self-contained, so no .NET install is needed.
| Platform | Archive | Size | Bundled into bin/ |
|---|---|---|---|
| Windows x64 | Nmkoder-<version>-win-x64.zip |
~490 MB | Everything: ffmpeg, MKVToolNix, av1an, VapourSynth, the encoders, grav1synth |
| Linux x64 | Nmkoder-<version>-linux-x64.tar.gz |
~180 MB | ffmpeg, SVT-AV1, VMAF models, grav1synth |
| macOS arm64 | Nmkoder-<version>-osx-arm64.tar.gz |
~58 MB | VMAF models, grav1synth |
| macOS x64 | Nmkoder-<version>-osx-x64.tar.gz |
~61 MB | VMAF models |
The Linux and macOS archives carry less and lean on your package manager; see
what a release bundles for the whole matrix and the reasons.
Anything you drop into bin/ yourself takes priority over the same tool on your PATH.
That decides what Quick Convert can re-encode out of the box, because it drives the standalone
encoder binaries rather than ffmpeg's libraries: the Windows archive covers all five, the Linux one
covers AV1 through SVT-AV1 only, and the macOS ones cover none until you
brew install aom x264 x265 libvpx. Anything else refuses the run naming the binary and the folder
to put it in (bin/av1an/enc), rather than quietly encoding with something else. Stream copy, GIF,
PNG, JPEG and NVENC go through ffmpeg and work regardless.
Each heading below expands.
Input
- Supports all formats that ffmpeg can decode
- Either use "Muxing Mode" to convert a single file or merge multiple files into one, or "Batch Processing Mode" to run an action on each file, with a naming template for the outputs
- Supports image sequence inputs (PNG/WEBP/JPEG/BMP) without requiring sequential filenames (FPS needs to be set manually)
- Drag and drop anywhere in the window, an "Add Folder" button, and a recent-files list
Track List
- View codec, language, title and more (depending on stream type) of the selected media stream
- Enable or disable streams with checkboxes - disabled streams are not included when encoding/muxing
- Re-order streams, and set the default audio and subtitle track
Quick Convert
- Video Formats: H.264 (x264 or NVENC), H.265 (x265 or NVENC), VP9 (vpxenc), AV1 (SVT-AV1 or AOM)
- The re-encoders are the standalone encoder binaries — the same x264, x265, SvtAv1EncApp, aomenc and vpxenc the AV1AN tab drives — fed over a pipe by ffmpeg, which still does the decoding, the filters and the final mux. NVENC, GIF, PNG, JPEG and stream copy stay on ffmpeg itself. A codec whose binary is not on the machine refuses the run naming it, rather than quietly encoding with something else
- Image Formats: Animated GIF, PNG Sequence, JPEG Sequence
- Audio Formats: AAC, Opus, Vorbis, E-AC-3, MP3, FLAC, globally or configured per track
- Text-based Subtitle Formats: Mov_Text for MP4/MOV, SRT for MKV, WebVTT for WEBM
- All media types also have the option to strip (remove) or copy (mux without re-encoding)
- Set metadata (title and language) for each track, and copy metadata or chapters from any loaded file
- Encoder Options: set quality and speed/effort aka preset, set colour format
- Advanced encoder arguments on a tab of their own, the same grid the AV1AN tab has: every parameter the encoder in front of you documents, grouped by category, each with a full explanation and example values on right-click. SVT-AV1's content presets are shared with the AV1AN tab, both driving the same binary, and a parameter that binary does not know stops the run up front rather than having the encode rejected once it has started. Below it, a Custom Video Filters grid for anything the rows above do not cover
- Quality Modes: a constant quality, a target bitrate, or a target filesize
- Video Options: resample the frame rate, resize from presets or an exact size, manually or automatically crop black bars, pad out to a target aspect ratio, and trim to a section picked while watching the frame you are on
- HDR to SDR tone mapping for PQ and HLG sources, with the roll-off built around the picture's measured brightness - on a GPU, libplacebo measures every frame as it goes; without one, a sampled scan reads the brightest real pixel first - and the output retagged BT.709
- Deinterlacing, its row shown only for files whose fields warrant it - a tape, DVD or camcorder capture - and off screen entirely for a modern progressive download. Uses QTGMC through VapourSynth where it can (bundled on Windows), otherwise ffmpeg's bwdif, and can output one frame per field so none of the motion is thrown away
- Film grain synthesis for the AV1 encoders, the same row as the AV1AN tab — the encoder's own analysis from a strength, or a grain table measured earlier — plus the one control that tab cannot afford: a Denoise tick beside the table, which reproduces the encode the table was measured for as one filter in a chain this tab is building anyway
- Audio Options: set quality and channels/layout
- Loudness normalization to a standard target (-14 / -16 / -23 LUFS, EBU R128), measured per track first and then applied as one flat gain, so the mix keeps its dynamics - a single-pass normalization reaches the same number by riding the gain, which lifts quiet passages by however much they were quiet
- Subtitle Options: optionally burn in a subtitle track
AV1AN Chunked Encoding
- Encode video using av1an and supported encoders
- Video Formats: AV1 (SVT-AV1 or AOM), H.265 (x265), H.264 (x264), VP9 (VPX)
- Quality Modes: a constant quality, or target a VMAF, SSIMULACRA2, Butteraugli or XPSNR score (experimental; SSIMULACRA2 needs a VapourSynth metric plugin, bundled on Windows, XPSNR is scored by ffmpeg, and Butteraugli currently needs the GPU plugin Vship, which the Windows bundle ships and enables per machine - see below)
- The same framing options as Quick Convert, trim included. Audio is one setting for every track
rather than Quick Convert's per-track configuration — av1an's own
-map 0carries the lot, so one codec, one bitrate and one channel layout apply to all of them, and there is no loudness normalization here; a file with more than one audio track has that stated before the run starts. Subtitles, data tracks and attachments are kept or dropped with checkboxes. Per-track titles and languages, the metadata and chapter source, and subtitle burn-in are Quick Convert's — remux there first if you need them - Deinterlacing too, its own setting, though without QTGMC: av1an filters each chunk with ffmpeg, which has nowhere to run a VapourSynth script, so QTGMC here would mean rendering the whole video into a lossless intermediate first - a serial pass that costs more than it saves, QTGMC being slower than the encoder on the tapes it exists for. Automatic and the ffmpeg deinterlacers run inside av1an; a tape that wants QTGMC goes through Quick Convert, which pipes it straight into the encoder, or through the Deinterlace Video utility
- HDR to SDR tone mapping as well, with the colour the encoder is told about following the conversion rather than the source. It runs on the CPU, per chunk inside av1an, on every machine - no render pass in front and no intermediate file, so the encode starts as soon as scene detection is done. This tab ran libplacebo as a whole-file pass for a few releases and no longer does: that pass was a full x264 re-encode of the film before av1an could start, hours on a feature, where the chunked chain costs nothing. The roll-off is built around a sampled scan of the picture's real brightness, which lands a few code values from the GPU result. libplacebo, and its Spline curve with it, are Quick Convert's - that tab runs one ffmpeg over the whole file, which is the continuous run peak detection needs
- An encode that stays HDR keeps its brightness metadata: the mastering display and the content light levels are handed to SVT-AV1 by flag, in the file's own units, since the bare frames av1an feeds an encoder cannot carry them
- Film grain synthesis for AV1 (the row is disabled for H.264/H.265/VP9, which have none): the encoder's own analysis from a strength, or a grain table measured off this source beforehand with the Film Grain utility - measuring is hours of single-threaded work, worth doing once per source rather than once per encode, and with the table in hand the encode itself costs nothing extra. The readout says whether the picture being coded is clean, which is what decides whether any of this saves bitrate or merely adds grain
- Advanced encoder arguments in a grid grouped by category, each with a full explanation and example values on right-click, plus content presets for anime and for game capture
- Av1an Options: change the splitting method, chunk creation method, number of workers, and more
- Encodes can be paused and resumed live, or stopped entirely and picked up again later from the finished chunks
Utilities
- Utilities are "shortcuts" for actions that normally require long (and/or multiple) CLI commands
- Read Bitrates: calculates stream size and average bitrate for each stream
- Sample Encodes (CRF ladder): answers "what CRF for this source" by trying it, in minutes rather than after the eight-hour encode you then regret. A few short sections are cut out from across the file, each is encoded at every CRF on the list, and the table reports the video bitrate, the size a whole file would come to, that size as a share of the source, and a VMAF, SSIMULACRA2, Butteraugli or XPSNR score per rung - plus which rung is the highest still worth using (VMAF 95, SSIMULACRA2 80, Butteraugli 4 - a distortion, so there lower is better and the pick is the highest CRF still under the line). SSIMULACRA2 and Butteraugli are scored through VapourSynth, the same way the AV1AN tab scores its Target modes (vszip for one, Vship or the julek plugin for the other, at the same 203-nit intensity as Target Butteraugli), so they need the Windows bundle - or VapourSynth with those plugins installed - and the run says so if it cannot. The sections are cut losslessly and each encode is scored against the cut it came from, so the picture measured and the picture encoded are the same frames. The Advanced tabs' content presets can be applied to the sample encodes too, so the ladder measures the encode actually being planned - SVT-AV1's are written for the AV1AN tab's svt-av1-hdr, so only the settings FFmpeg's own SVT-AV1 takes are applied, and the run names what did not carry. Its own encoder, preset, colour format, CRF list and sampling, under Configure
- Get Metrics: calculate quality metrics like VMAF, SSIM, PSNR
- Transfer Color Metadata: copy colour properties and HDR metadata from one file to another (e.g. from a Bluray remux to an encode)
- Deinterlace Video: exports a deinterlaced copy - QTGMC over the whole file into a near-lossless MKV, with the audio and subtitles copied across. That file is all it produces; nothing is loaded back into the file list, and you do not need it in order to encode an interlaced source, since both encode tabs deinterlace on their way through. Has its own Deinterlace settings, under Configure on its card, separate from either tab's
- Film Grain (AV1): the parts of a grain workflow that are not an encode. Measure a grain table off any source, whatever its codec - it compares decoded frames - or, on an AV1 file, extract the table it already carries, apply grain to it (from a table, one of grav1synth's film stock presets, or photon noise at an ISO), or remove every grain header from it. The last three rewrite the AV1 headers and remux; nothing is re-encoded and the picture is untouched
- Cut Video: keep only a chosen section, copied out without re-encoding, with the start and end points picked while watching the frame you are on
- Concatenate Into Single MKV: merge any amount of any compatible video format into a single MKV (e.g. for chunked encoding)
- Show Bitrate Chart: samples the bitrate across the entire video and plots it, so you can see where bitrate is higher or lower
- OCR Bitmap Subtitles: converts selected bitmap-based subtitle tracks into text subtitles
Each heading below expands.
The application
- Ported from WinForms to Avalonia on .NET 10, with native builds for
win-x64,linux-x64,osx-x64andosx-arm64. Linux and macOS are first-class rather than a WINE suggestion. - A dark theme throughout, rather than the system's default control colours.
- A release pipeline: every build is published self-contained and portable, with the external
tools staged into
bin/and the job summary listing exactly what each archive shipped. - The log is its own panel - per-line severity colouring, copy, save, clear, and a button that opens the session's log folder, which also holds the raw ffmpeg, av1an and mkvmerge logs.
- The window remembers itself: size, position, open tab and log height, plus the last browsed folders and a recent-files list. Neither encode tab's settings persist - both start each session at their defaults, because a CRF, a resize or a QTGMC pass left armed from last week is expensive and quiet. Quick Convert opens on SVT-AV1 into MKV at CRF 30, preset 4, with audio to Opus at 128 kbps stereo.
Deinterlacing
None of this existed before the fork - there was no deinterlacing in the app at all.
- Sources are checked rather than assumed. The container's field-order flag is read first - an
MPEG-2 tape capture or a DV file says what it is - and where it says nothing, a few hundred frames
are decoded through ffmpeg's
idet, then measured field-gap by field-gap so that a vertical pan over fine detail is not mistaken for combing. - The Deinterlace row only appears for files whose fields warrant it, so a modern download never shows the control and a tape capture arrives with the right engine already selected.
- QTGMC, the motion-compensated deinterlacer, run through VapourSynth (bundled on Windows) with the whole plugin chain it needs, falling back to ffmpeg's bwdif where VapourSynth cannot run it. Optionally one frame per field, so none of the motion is thrown away.
- Both encode tabs deinterlace. Quick Convert pipes QTGMC straight into its encoder; the AV1AN tab offers the ffmpeg deinterlacers only, because av1an filters each chunk with ffmpeg, which has nowhere to evaluate a VapourSynth script - QTGMC there would be a serial render of the whole video before the parallel encode began, which costs more than it saves on the captures QTGMC exists for.
- A Deinterlace Video utility for when the deinterlaced file itself is what you want, with its own settings separate from either tab's.
HDR tone mapping
- HDR sources can be converted to SDR, on both encode tabs. The row only appears for a file whose transfer curve says PQ or HLG, and it defaults to doing nothing - the other reason to load an HDR file is to re-encode it as HDR - so it exists to say the file is HDR and let you choose.
- The roll-off is built around the picture's measured brightness, not the metadata's claim. The ordinary UHD Blu-ray declares a 4000-nit mastering display and a MaxCLL near the format ceiling over frames that top out around 600 nits, and a mapping priced for the metadata renders the whole film 30-odd code values darker than a player that measures the signal - which is exactly what mpv does, and why the source "looked brighter" there. Where libplacebo runs, its peak detection measures every frame; on the CPU chain a sampled scan reads the brightest real pixel off the file and the declared value only caps it. None of this can be left to FFmpeg itself: measured, its tone-mapper reads no metadata at all - the same clip with and without MaxCLL and a mastering display tone-maps identically - and the widely-copied chain's default clips everything above about 374 nits to flat white. The encode log states measured, declared and effective peaks per file.
- Which of the two backends runs is Quick Convert's question alone. There it is the machine's: libplacebo where a real GPU passes the probe (a software Vulkan device tone-maps at a tenth of the speed and is refused), FFmpeg's own zscale chain otherwise, settled once per encode and stated in the log. The AV1AN tab always uses the zscale chain, per chunk - a whole-file libplacebo pass there meant re-encoding the film before av1an could start. That is also why Spline is on Quick Convert's curve list only: it is libplacebo's own curve and has no equivalent on the CPU.
- It runs before any crop, scale or burnt-in subtitle, so subtitles are not dragged through a gamut conversion written for the picture, and the output is retagged BT.709 with the HDR metadata dropped - static and dynamic alike, HDR10+ included - including on the AV1AN tab, where the encoders are handed colour as numbers and would otherwise write SDR pixels into a file tagged HDR.
- The previews show the tone-mapped picture. Thumbnails, the Cut window's scrubber and the crop preview used to draw PQ code values as though they were BT.709 - washed out and grey, for exactly the files this row exists for. Display-side only; no encode reads it.
- Dolby Vision is read off the file. A profile whose base layer plays without the RPU is tone-mapped as the ordinary HDR10 or HLG underneath it. Profile 5, whose base layer is not YCbCr at all, is refused on the CPU chain rather than encoded to magenta and green - so it is refused on the AV1AN tab on every machine, and the message points at Quick Convert, where libplacebo reads these files: FFmpeg parses the RPU itself and hands it to libplacebo's reshape shader, measured on a real profile 5 stream against the bundled build, which carries no libdovi at all. The refusal deliberately does not suggest converting the file first: a profile conversion rewrites the metadata and moves no pixels, so it buys nothing - and it turns a loud refusal into a silently wrong output, the converted file declaring a profile this check lets through.
Film grain synthesis
AV1 can describe film grain in a few bytes and have the decoder regenerate it at playback, which on grainy film is the single largest saving there is - but only where the picture being coded has had the grain taken out of it first. Before the fork this was one spinner writing one encoder flag.
- The Grain Synthesis row is now a mode selector, and that is not cosmetic: SVT-AV1 has three
ways to be asked for grain (
--film-grain,--noise,--fgs-table) and takes exactly one of them, discarding the others with a warning that goes to a log the app deletes on success. One control that writes at most one of them cannot express that collision. What can still collide - a parameter typed into the Advanced grid beside it - is reported before the encode, naming which of the two wins. Both encode tabs carry the row now. - A grain table can be measured off your own source rather than guessed at by the encoder. The file is denoised, grav1synth measures the difference, the encoder is handed the clean picture and the table, and the grain comes back at playback. Measured on a test clip at CRF 35: 977 KB encoding the grainy source, 743 KB encoding the denoised one, 759 KB with the grain described back into it.
- Measuring happens once per source, not once per encode. It used to be an encode mode - hours of single-threaded measuring in front of av1an, repeated on every run. It is the Film Grain utility's Measure operation now, which states the estimate for the loaded file before you commit (grav1synth's diff runs at about 7.2 megapixels a second, so a 1080p feature is a working day) and can keep the denoised copy beside the table: the file to encode, with the table to put back. The table then feeds either tab's Grain table file mode, and describes the source, so every later encode of the same film reuses it for nothing.
- The row is what the encoder does while it encodes, and costs no pass of its own. Grain written into a file that is already encoded - a film stock preset, photon noise, or a table applied afterwards - is the Film Grain utility's job instead, the same division Cut and Deinterlace Video already draw.
Using film grain synthesis
Which of these you want depends on what you have and what you are willing to spend.
Just encode with grain, cheaply — either encode tab → Grain Synthesis → Encoder analysis, pick a strength (10 for lightly grainy digital, 25 for an ordinary film scan) and tick Denoise. The encoder does the whole thing itself: no extra tool, no extra pass. Untick Denoise and you get grain added to grain already in the picture, which costs bitrate rather than saving it - that is what the readout means when it says the source's own grain is coded too.
Measure the grain this source actually has — Utilities → Film Grain (AV1) → Measure. The file is
denoised, grav1synth measures the difference, and the table comes out as <name>.grain.tbl - optionally
with the denoised copy kept beside it, which is the other half of the pipeline: the file to encode, with
the table to put back. More accurate than the encoder's own guess, and much slower: the dialog gives the
estimate for the file you have loaded, and it is hours for a feature. Any codec will do - it compares
decoded frames, so it reads grain off a ProRes master or a DVD rip as well as an AV1 file.
Encode with that table — Grain Synthesis → Grain table file on either tab, pointed at the kept
.tbl. On Quick Convert, tick Denoise at the same strength the table was measured with to reproduce
the encode it describes; the AV1AN tab runs no denoise pass of its own, so encode the utility's kept
denoised copy there instead. Leave Denoise unticked only if the table is there to add grain to a source
that never had any.
Work on a file that is already encoded — Utilities → Film Grain (AV1). Extract the table an AV1 file already carries, apply grain to one from a table, a film stock preset or an ISO, or strip its grain entirely. The last three rewrite the AV1 headers and remux - nothing is re-encoded and the picture is untouched.
Only the utility needs grav1synth; Encoder analysis works on every AV1 build with nothing installed, and a table the encoder in front of it cannot take is refused by name rather than failing partway through an encode.
Framing: resize, crop, borders and trim
- Resize is a dropdown with presets - 2160p through 360p as boxes the picture is fitted inside, 75/50/25% proportions, and a Custom dialog for exact sizes with letterbox or stretch - replacing two free-text width/height boxes. Underneath it, a per-file readout of the frame the encoder will actually be handed.
- Anamorphic sources are de-squeezed where the encoder cannot carry the flag, upscaling is stated rather than silent, and a target ffmpeg cannot scale to is refused up front instead of failing one chunk at a time.
- Crop keeps the rectangle inside the frame and on the chroma grid, and a crop too large for the file - the usual way a batch goes wrong, four edges outliving the file they were set for - stops the run naming the file and the numbers.
- Borders: pad out to a target aspect ratio (16:9, 4:3, 1:1, 9:16, 21:9) without scaling. Which bars are needed is worked out per file, so a 2.39:1 film and a 4:3 capture both reach 16:9 from one dropdown entry. Applied after the crop and the resize, so a scaler never runs over a hard edge.
- Trim is picked visually. The in/out dialog shows the frame at the playhead while you choose the section, in the shape LosslessCut made familiar, and the same dialog serves Quick Convert, the AV1AN tab (which had no trim at all before) and the lossless Cut utility. All three modes — seek straight to the point, decode from the start, or the same again in frame numbers — begin on exactly the frame you set, measured against the bundled FFmpeg rather than assumed; what they differ in is how long they take to get there. Where the section is copied rather than re-encoded — the Cut utility, the AV1AN trim, and Quick Convert with the video codec on Copy — there is no mode to choose: a copy can only begin at a keyframe, so the dialog moves the start point back onto the nearest one and shows where it landed. Either way a section that does not fit the file is caught before the encode rather than producing an empty output.
The AV1AN tab
- Target SSIMULACRA2, Target Butteraugli and Target XPSNR quality modes alongside Target VMAF, each checked up front against what the installed av1an and its plugins can actually score.
- Vship staged per machine on Windows: the GPU check runs before a metric-targeted encode and installs the build this machine passes, so Butteraugli works out of the box and SSIMULACRA2 moves to GPU scoring where it can.
- x264 joins aomenc, SVT-AV1, x265 and vpxenc as an encoder av1an can drive from here.
- The advanced argument grid is grouped into categories, and every argument carries a full explanation and example values on right-click, rather than a one-line hint in a flat list.
- Content presets for the argument grid - Anime / Cel Animation and Game Capture / Gameplay - written for the SVT-AV1 PSY line the release bundles. Anything the binary in front of them does not support is dropped as the preset is applied, and a parameter typed by hand that the encoder does not know stops the run instead of having av1an reject the whole command.
- Workers and threads-per-worker are derived together from the core count instead of one being a
literal, SVT-AV1 runs two workers fewer than the others because it loads a core harder, and
"Threads per Worker" now actually limits x265 (which has no
--threads, only--pools). - Scene detection runs in parallel slices ahead of av1an. Detection is the one phase the workers cannot help with - av1an runs it alone over every frame while the rest of the machine idles, about as long as the encode itself on a 4K60 file - so the source is sliced, detected concurrently, and the merged list handed over. The slice count has its own box on Av1an Options, and any missing piece falls back to av1an's own in-run detection.
- Progress is measured from av1an's own temp folder - chunk counts out of
scenes.jsonanddone.json- rather than parsed out of log lines that av1an stopped printing several releases ago. - Frame-rate resampling no longer kills a run partway through, av1an's log lands in the encode's temp folder instead of beside the binary, and a target-quality mode meeting a filter chain says so (av1an's probes never see the filters).
Quick Convert
- The re-encoders are the real binaries now. x264, x265, SvtAv1EncApp, aomenc and vpxenc - the same executables the AV1AN tab drives - are fed by ffmpeg over a pipe, then muxed back with the audio, subtitles, chapters and metadata that never went down it. The same encoder on both tabs means a CRF means the same thing on both; a missing binary refuses the run by name rather than quietly substituting ffmpeg's library for the encoder you picked. NVENC, GIF, PNG, JPEG and stream copy stay on ffmpeg.
- The command is built in one place, in an order that holds together: stream maps that know
whether a filtergraph exists, per-input trim arguments,
-metadata:s:Nindices that count the streams which actually reach the output, and a two-pass stats log written into the session folder rather than the working directory. - Target Filesize divides by the right numbers - the trimmed duration, and a copied track's own bitrate rather than whatever the disabled bitrate spinner happened to hold.
- Per-track audio configuration seeds from the channel layout you asked for, and the dropdown it overrides is disabled while it governs rather than left looking live.
- Paths survive the shell and the filtergraph. Burning in a subtitle track from a path containing
a space, a colon, an apostrophe, an
=, a$or a backtick works on all three platforms; it previously failed on most of them, and on Windows on every path there is.
Audio
- Loudness normalization (EBU R128) on the Quick Convert tab, to -14, -16 or -23 LUFS. Each track is
measured in a pass of its own and then encoded with a single flat gain, which is the part that matters:
ffmpeg's one-pass
loudnormhits the same number by riding the gain, and measured on a source whose quiet passage sat 26 dB under its loud one it brought the two to within 1.3 dB of each other. The channel conversion runs inside the same filter, because the app's own downmix would otherwise happen afterwards and leave a 5.1 source 7.7 dB off the target it asked for.
Utilities
- Sample Encodes - the CRF ladder: short sections from across the source, encoded at several CRFs, reported as size-per-minute, a whole-file projection and a VMAF, SSIMULACRA2, Butteraugli or XPSNR score each. This is the thing a GUI can do that a command line will not: picking the CRF by measuring the source rather than by guessing and finding out at the end. The samples are cut losslessly and scored against those cuts, so nothing is being compared across a resize or a seek. SSIMULACRA2 and Butteraugli are scored through VapourSynth (the AV1AN tab's Target mechanisms - vszip, and Vship or julek), so they are Windows-only unless you have VapourSynth and those plugins installed - the run refuses with a reason where it cannot compute them. Butteraugli is a distortion on the same 203-nit scale as Target Butteraugli: 0 is identical and lower is better. The Advanced tabs' content presets can be applied to the samples, so the ladder measures the encode being planned rather than a vanilla one - with SVT-AV1's translated as far as FFmpeg's own SVT-AV1 takes them, and the rest named.
- Cut Video - keep a chosen section, copied out without re-encoding, picked in the same visual dialog as the trim.
- Deinterlace Video - a deinterlaced copy into a near-lossless MKV with audio and subtitles carried across.
- OCR Bitmap Subtitles is now a card on the Utilities tab; the code was there before but nothing in the UI reached it.
While a job runs
- Pause actually pauses, freezing the whole process tree rather than the launcher, and Stop ends a run cleanly.
- Live progress in the footer with an ETA, whichever tool is running. QTGMC's progress is measured against the frame count VapourSynth reports rather than the duration in the container, because a tape capture's duration is whatever its capture card claimed - and when a run proves its own target wrong, the bar says so instead of sitting at 100%.
- The OS notifies you when a run ends and the window is not in the foreground, through
notify-send,osascriptor the Windows App SDK. - What the encode did to the file size is reported when it finishes.
- Shutdown when done, with a 60-second countdown you can call off.
- Batch mode works, with per-file status in the file list and output-name templates
(
{name},{codec},{crf},{index},{date}and more) - a batch overwrites the output box per file, so the template is the only say you get in what twelve files are called.
Fixes worth naming
- The Metrics utility had been passing the VMAF model file as libvmaf's log path, which scored every run against the built-in default and overwrote the bundled model with an XML log - which av1an was then pointed at. Models are selected by version now, and no path goes into that filter.
- The bitrate readout reported
Size: 0B (0.0%)for every stream, ffmpeg having renamed the unit in its stats line fromkBtoKiB. - Colour metadata is read using the names ffprobe actually prints and re-spelled per encoder, aomenc refusing a name it does not know by encoding nothing at all.
- Every HDR-preserving SVT-AV1 encode had been coming out declaring no peak brightness at all - the mastering display and content light levels were simply never handed over, and the file still said PQ, so nothing looked wrong until it was played on real hardware. Both go to the encoder by flag now, in the file's own units.
- A failed av1an run quotes the tail of av1an's own log in the failure message; the visible-console mode had been reporting "exit code 1" with the actual error gone unread, the console closing itself five seconds after the death.
- A missing mkvmerge - routine on Linux and macOS - is detected before it is needed, rather than showing up as an av1an encode that quietly finished without audio or a concat that failed naming a temp path.
- Animated GIF could not be produced at all unless some other filter happened to be configured.
- Windows 10/11 64-bit is the primary target. Since the move from WinForms to Avalonia the app also builds and runs natively on Linux and macOS.
- Released archives are self-contained: no .NET install is required. Building framework-dependent yourself needs the .NET 10 runtime.
ffmpeg,ffprobe,mkvmerge,av1anandgrav1synthare looked up in thebinfolder next to the executable first, then onPATH. The encoder binaries both tabs drive —SvtAv1EncApp,aomenc,vpxenc,x264,x265— are looked for inbin/av1an/encfirst and then onPATH; that is where to drop your own, and it is the folder a refusal names when a codec's binary is in neither place.
Portable builds are produced by .github/workflows/release.yml. Push a v* tag to publish a
release, or run the workflow manually with a version: that produces a draft, and ticking publish
creates the v<version> tag and a public release instead. .github/scripts/bundle-tools.sh stages
the external tools into bin/:
| ffmpeg / ffprobe | MKVToolNix | av1an | VapourSynth | SVT-AV1 | aomenc, x264, x265 | vpxenc | VMAF models | grav1synth | |
|---|---|---|---|---|---|---|---|---|---|
| win-x64 | bundled | bundled | bundled | bundled | bundled | bundled | bundled | bundled | built from source |
| linux-x64 | bundled | use package manager | cargo install av1an, see below |
use package manager | bundled | use package manager | use package manager | bundled | built from source |
| osx-x64 / osx-arm64 | brew install ffmpeg |
brew install mkvtoolnix |
cargo install av1an |
brew install vapoursynth |
build from source, see below | brew install aom x264 x265 |
brew install libvpx |
bundled | arm64 only, see below |
Tool downloads are best-effort: an unreachable upstream is reported and skipped rather than failing the release, and the workflow's job summary lists exactly what each build shipped. Read that summary rather than this table — it is the record of what a given archive actually got, where this is what the bundler goes looking for.
av1an on Linux is exactly that distinction, and the table says so rather than promising: the bundler
does search for a Linux asset, but av1an publishes its binaries on a rolling latest prerelease whose
assets do not always include one it can match, and recent releases have shipped with
[skip] av1an - no usable release binary in rust-av/Av1an for linux-x64 in the job summary. So install
it yourself on Linux for now. The AV1AN tab looks in bin/av1an/ first and then on PATH, so either
place works.
grav1synth is compiled, not downloaded
grav1synth is what reads and writes the film grain description inside an AV1 bitstream, and the Grain Synthesis row needs it for anything but the encoder's own analysis. It has never cut a release - there is not one tag on the repository - so there is no binary to fetch and the release workflow builds it from a pinned commit, which is the only tool here that needs a compiler on the runner.
Two consequences worth knowing:
- osx-x64 does not get it. Compiling produces a binary for the host, and GitHub's macOS runners
are arm64, so an osx-x64 build would ship an arm64 binary inside an Intel archive. The bundler
skips it and says so rather than doing that.
cargo install --git https://github.com/rust-av/grav1synth --rev 1044228cd411672b565e5762a9b3597f4dd163b0puts one on yourPATHif you want it there — the same commit the release builds, pinned rather than trackingmainbecause this tool rewrites an AV1 bitstream and an upstream regression would surface in somebody's finished encode. Cargo fetches the repository'sdav1d-test-datasubmodule on the way, which the build never reads; a shallow clone of that commit skips it and produces the same binary, which is what the release workflow does. - The Windows archive carries FFmpeg's shared libraries because of it, about 168 MB uncompressed. The only FFmpeg build that ships headers and import libraries is the shared one, so a Windows grav1synth links against DLLs and cannot start without them beside it. They are copied in before the build is smoke-tested, and removed again with it if it still will not run.
Without it, everything else still works: the encoder's own grain synthesis needs no tool at all, and the modes and the utility that do name the missing binary and why rather than failing mid-encode.
SVT-AV1 comes from the PSY line
Nmkoder bundles the svt-av1-hdr build, which continues
the SVT-AV1-PSY line, and the AV1AN tab's content presets are written for that line's parameters and
defaults. Mainline SVT-AV1 - which is what Homebrew's svt-av1 is - has absorbed much of the PSY
surface by now, but not all of it: the noise family, fgs-table, tx-bias and
noise-adaptive-filtering are still the fork's alone, and a mainline binary rejects a parameter it
does not know outright rather than ignoring it - so Nmkoder asks the binary and drops what it lacks,
saying so in the log. What no check can put back is the defaults: features mainline accepts it keeps
off (enable-qm, variance boost) where the PSY line has them on, so the same command means less
there. On macOS, build svt-av1-hdr from source and put SvtAv1EncApp on your PATH to get the
presets as intended. There is deliberately no mainline fallback: a substituted binary under the same
filename is worse than a visible skip.
VapourSynth, and what needs it
The portable VapourSynth build is Windows-only, and it is what QTGMC deinterlacing and the VapourSynth chunk methods run on. On Linux and macOS, install VapourSynth through your package manager; without it, QTGMC falls back to bwdif and says so.
Target SSIMULACRA2 scores its probes through the
vszip VapourSynth plugin (or the GPU-accelerated
vship), which has to be installed into VapourSynth's plugin
directory by hand on Linux and macOS - without it, that quality mode fails once av1an starts probing.
Target Butteraugli needs vship specifically, for the reason below. Target XPSNR needs no plugin:
av1an scores it with ffmpeg's xpsnr filter, present in the bundled ffmpeg and in any FFmpeg from
7.1 on.
The caveat on Butteraugli: every av1an release to date calls the CPU scoring plugin
(julek) by the wrong function name
(butteraugli where the plugin registers Butteraugli), so that path fails at probe time no matter
what is installed. Until av1an fixes the invoke, Target Butteraugli works only through
vship. The Windows bundle therefore ships both Vship builds
parked in vsynth/vship, outside the autoload folder, and before a metric-targeted encode the app
runs Vship's own GPU check and stages the build this machine passes into vs-plugins - so
Butteraugli works out of the box on a capable NVIDIA or AMD GPU, and SSIMULACRA2 moves to GPU scoring
on those same machines, since av1an prefers Vship wherever it sees it. A machine no build passes on is
stopped up front with the working alternatives named. The staged copy carries nmkoder's own file name
(nmkoder-vship_*.dll), so a Vship you install into vs-plugins yourself - under upstream's names or
any other - is recognised as yours: the app withdraws its own copy and never touches your file. Where
there is no bundled plugin folder to manage (Linux/macOS), the app only warns.
The staged layout
The chunked-encoding toolchain is staged in the layout the app runs it from:
bin/av1an/av1an[.exe] av1an itself
bin/av1an/vsynth/ VapourSynth + embedded Python (VSPipe)
bin/av1an/vsynth/vs-plugins/ BestSource, L-SMASH-Works and FFMS2, for the matching chunk methods,
vszip, which scores Target SSIMULACRA2 probes and Sample Encodes'
SSIMULACRA2 column, julek, which Nmkoder calls itself for Sample
Encodes' Butteraugli column and which is also parked here for av1an's
Target Butteraugli against the day it can call it (see the caveat above),
and mvtools, znedi3, EEDI3, fmtconv, RemoveGrain, MiscFilters,
TemporalSoften2 and FFT3DFilter, which are what QTGMC deinterlacing
is made of (FFT3DFilter only for its two denoising presets)
bin/av1an/vsynth/vship/ Vship's NVIDIA + AMD builds, parked; the app stages the one this
machine's GPU passes into vs-plugins, and unstages both when none does
bin/av1an/enc/ SvtAv1EncApp, aomenc, vpxenc, x264 and x265 - the encoders both
encode tabs drive
grav1synth is not part of that tree - it sits in bin/ beside ffmpeg and mkvmerge, which is where the
app looks for it, with FFmpeg's shared libraries next to it on Windows.
vsynth and enc are prepended to av1an's PATH, so nothing needs installing system-wide.
DGDecNV is the one chunk method left uncovered - it needs a licensed DGDecNV install.
Overriding the sources
Binaries are resolved from each project's releases at build time. SvtAv1EncApp comes from
svt-av1-hdr's own release assets, for the reason in
SVT-AV1 comes from the PSY line above; aomenc, x264 and x265, which upstream does not publish for
Windows, come from MSYS2's mingw64 packages. MSYS2 packages an SVT-AV1 too, and it is deliberately
left off that list: it is mainline, so installing it under the same filename would silently
substitute for the PSY build the presets are written for.
Override the sources with the AV1AN_REPO, SVTAV1_REPOS, VAPOURSYNTH_REPO, LSMASH_REPO,
FFMS2_REPO, BESTSOURCE_REPO, VSZIP_REPO, VSZIP_TAG, VSJULEK_REPO, VSJULEK_TAG,
VSHIP_REPO, VSHIP_TAG, PYTHON_EMBED_VERSIONS, MSYS2_ENCODERS, MSYS2_ROOT, GH_RELEASE_SCAN
and MKVTOOLNIX_VERSION environment variables.
vpxenc has no upstream Windows build
No project publishes a prebuilt Windows vpxenc: the WebM project ships source only,
ShiftMediaProject builds the library rather than the CLI, and MSYS2's libvpx package leaves the
encoder out. Windows builds therefore take a community build of libvpx - shared in the AV1 community
Discord, and inspected before it was used here.
It is fetched from a release asset of this repository (VPXENC_REPO, defaulting to the repo being
built), pinned by SHA1 through VPXENC_ASSET_SHA1 and looked for on a named tag through
VPXENC_ASSET_TAG, since an asset attached once slides out of the most recent handful of releases
after a while and would otherwise stop being found. Replacing the asset means updating that hash;
anything else is rejected rather than shipped.
The fallback is https://jeremylee.sh/bins/vpx.7z, the build the av1an ecosystem uses. That is one
person's server with no signed provenance and a certificate that has expired before now - downloads
are made with certificate verification on, so if it does not validate when a release is built, vpxenc
is reported as a failed download and skipped rather than fetched insecurely. VPXENC_SHA1 pins that
route too, left unset by default because the build rolls forward and a fixed hash would reject every
future one.
Either way nothing is trusted just because it downloaded: the bundler verifies the staged file really
is a Windows executable, and bin/THIRD-PARTY.txt records which source it came from and that it is a
community build rather than an official one.
Point VPXENC_URL at a different build (a bare .exe or an archive containing one) to override the
fallback, or set it empty (VPXENC_URL=) to skip that route entirely. Without vpxenc, VP9 has no
encoder behind it on either tab - both drive the same binary, and Quick Convert refuses the run
naming it rather than substituting ffmpeg's library.
Data files
bin/iso639.csv, the language table that names audio and subtitle tracks, is not downloaded - it
lives in Nmkoder/BinFiles and every build copies it into bin. Regenerate it with
.github/scripts/gen-iso639.py when the ISO registers move. The same folder carries both encode
tabs' per-encoder argument lists, under BinFiles/encoderArgs, in two folders because they are two
vocabularies: av1an/ names the CLI parameters the standalone encoder binaries take — read by the
AV1AN tab and by Quick Convert alike, so its x264 reads av1an/X264.json — and ffmpeg/ names what
ffmpeg's own wrappers accept, which is what NVENC and the Sample Encodes CRF ladder read. One JSON
per encoder inside each.
dotnet build Nmkoder.sln -c Release
To produce a self-contained, single-file build for a given platform:
dotnet publish Nmkoder/Nmkoder.csproj -c Release -r win-x64 --self-contained -p:PublishSingleFile=true
dotnet publish Nmkoder/Nmkoder.csproj -c Release -r linux-x64 --self-contained -p:PublishSingleFile=true
dotnet publish Nmkoder/Nmkoder.csproj -c Release -r osx-arm64 --self-contained -p:PublishSingleFile=true
The project multi-targets net10.0 everywhere plus net10.0-windows10.0.19041.0 when the host is
Windows; that second target framework is the one carrying the Windows App SDK, used for notifications.
Nmkoder was written by N00MKRAD; this is a fork of
n00mkrad/nmkoder. Licensed under the GPL-3.0 - see
LICENSE. The bundled third-party tools keep their own licences, recorded in
bin/THIRD-PARTY.txt in every release archive.





