Converting video with FFmpegCore
Tags: fsharp, net, ffmpeg, multimedia
Published: 2021-03-16 09:09:02

---
title: "Converting video with FFmpegCore"
published: true
description: "This article shows how you can use FFmpegCore to process your video"
tags: ["Fsharp", "NET", "Ffmpeg", "Multimedia"]
cover_image: https://navigator-ukraina.com.ua/media/k2/items/cache/fe392f78a62c6fc460cf8c2a182b395f_L.jpg
---
Working with multimedia is terra incognita for most of the developers since it's something that one rarely encounters while working with usual business applications. So when I was tasked to convert video for the project I'm currently working on I was expecting to deal with some sort of old poorly maintained C++ library. So [FFmpegCore](https://github.com/rosenbjerg/FFMpegCore) was a pleasant surprise since it enables working with .Net Core which is an area of my expertise.

Examples in this article will be provided in F# [which I'm a big fan of](https://wkalmar.github.io/post/querying-last-fm-web-api-with-fsharp/) but they are pretty straightforward so it should be no problem in translating them to C#.

## Installing FFmpeg
While documentation reads that this core is "A .NET Standard FFMpeg/FFProbe wrapper" this tells nothing about the fact that Ffmpeg/fmprobe should be installed on the machine where the application is running.

Furthermore, I think it is worth clarifying that [FFMpeg](https://ffmpeg.org/) is a cross-platform command-line tool that allows working with video.

While on Linux once you do `apt install` you're good to go on Windows there is a point of interest. Installing FFmpeg on Windows is a matter of downloading the binaries and putting them in a folder you will but once running the tool you may face the error:
```
system.componentmodel.win32exception: the system cannot find the the file specified
```

which is fixed with the help of static class `FFMpegOptions`
```ocaml
let options = FFMpegOptions()
options.RootDirectory <- "path to your binaries"
FFMpegOptions.Configure(options)
```

## Querying video information
In order to query information about the video, we use static `FFProbe` which has both synchronous and asynchronous API for video analysis. Let's stick with the async version and serialize the output to examine the wealth of information that `FFProbe` provides us.
```ocaml
async {
    let! videoInfo = FFProbe.AnalyseAsync fileName |> Async.AwaitTask
    return JsonSerializer.Serialize videoInfo
}
```
The output may be as rich a below
```
{
   "Path":"D:\\giphy.mp4",
   "Extension":".mp4",
   "Duration":{
      "Ticks":17200000,
      "Days":0,
      "Hours":0,
      "Milliseconds":720,
      "Minutes":0,
      "Seconds":1,
      "TotalDays":1.990740740740741E-05,
      "TotalHours":0.00047777777777777776,
      "TotalMilliseconds":1720,
      "TotalMinutes":0.028666666666666667,
      "TotalSeconds":1.72
   },
   "Format":{
      "Duration":{
         "Ticks":17200000,
         "Days":0,
         "Hours":0,
         "Milliseconds":720,
         "Minutes":0,
         "Seconds":1,
         "TotalDays":1.990740740740741E-05,
         "TotalHours":0.00047777777777777776,
         "TotalMilliseconds":1720,
         "TotalMinutes":0.028666666666666667,
         "TotalSeconds":1.72
      },
      "FormatName":"mov,mp4,m4a,3gp,3g2,mj2",
      "FormatLongName":"QuickTime / MOV",
      "StreamCount":1,
      "ProbeScore":100,
      "BitRate":458339,
      "Tags":{
         "major_brand":"isom",
         "minor_version":"512",
         "compatible_brands":"isomiso2avc1mp41",
         "encoder":"Lavf56.40.101"
      }
   },
   "PrimaryAudioStream":null,
   "PrimaryVideoStream":{
      "AvgFrameRate":25,
      "BitsPerRawSample":8,
      "DisplayAspectRatio":{

      },
      "Profile":"Constrained Baseline",
      "Width":480,
      "Height":264,
      "FrameRate":25,
      "PixelFormat":"yuv420p",
      "Rotation":0,
      "Index":0,
      "CodecName":"h264",
      "CodecLongName":"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
      "BitRate":453744,
      "Duration":{
         "Ticks":17200000,
         "Days":0,
         "Hours":0,
         "Milliseconds":720,
         "Minutes":0,
         "Seconds":1,
         "TotalDays":1.990740740740741E-05,
         "TotalHours":0.00047777777777777776,
         "TotalMilliseconds":1720,
         "TotalMinutes":0.028666666666666667,
         "TotalSeconds":1.72
      },
      "Language":"und",
      "Tags":{
         "language":"und",
         "handler_name":"VideoHandler",
         "vendor_id":"[0][0][0][0]"
      }
   },
   "VideoStreams":[
      {
         "AvgFrameRate":25,
         "BitsPerRawSample":8,
         "DisplayAspectRatio":{

         },
         "Profile":"Constrained Baseline",
         "Width":480,
         "Height":264,
         "FrameRate":25,
         "PixelFormat":"yuv420p",
         "Rotation":0,
         "Index":0,
         "CodecName":"h264",
         "CodecLongName":"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
         "BitRate":453744,
         "Duration":{
            "Ticks":17200000,
            "Days":0,
            "Hours":0,
            "Milliseconds":720,
            "Minutes":0,
            "Seconds":1,
            "TotalDays":1.990740740740741E-05,
            "TotalHours":0.00047777777777777776,
            "TotalMilliseconds":1720,
            "TotalMinutes":0.028666666666666667,
            "TotalSeconds":1.72
         },
         "Language":"und",
         "Tags":{
            "language":"und",
            "handler_name":"VideoHandler",
            "vendor_id":"[0][0][0][0]"
         }
      }
   ],
   "AudioStreams":[

   ]
}
```

## Converting video
In order to convert video, one uses static `FFMpegArguments` class which enables some sort of static builder pattern. Again it exhibits both synchronous and asynchronous API and we'll stick to the latter
```ocaml
async {
    let! _ =
        FFMpegArguments
            .FromFileInput(fileName)
            .OutputToFile(outputFileName,
                true,
                fun options -> options
                                .WithVideoCodec(VideoCodec.LibX264)
                                .WithAudioCodec(AudioCodec.Aac)
                                .WithVariableBitrate(4)
                                .Resize(newWidth, newHeight)
                                |> ignore)
            .ProcessAsynchronously() |> Async.AwaitTask
    ()
}
```
Upon some circumstances `FFMpeg` may return an error.
```
"ffmpeg version 2021-01-24-git-1775688292-full_build-www.gyan.dev Copyright (c) 2000-2021 the FFmpeg developers\n  built with gcc 10.2.0 (Rev6, Built by MSYS2 project)\n  configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-lzma --enable-libsnappy --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libglslang --enable-vulkan --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint\n  libavutil      56. 63.101 / 56. 63.101\n  libavcodec     58.117.101 / 58.117.101\n  libavformat    58. 65.101 / 58. 65.101\n  libavdevice    58. 11.103 / 58. 11.103\n  libavfilter     7. 96.100 /  7. 96.100\n  libswscale      5.  8.100 /  5.  8.100\n  libswresample   3.  8.100 /  3.  8.100\n  libpostproc    55.  8.100 / 55.  8.100\nInput #0, mov,mp4,m4a,3gp,3g2,mj2, from 'D:\\giphy.mp4':\n  Metadata:\n    major_brand     : isom\n    minor_version   : 512\n    compatible_brands: isomiso2avc1mp41\n    encoder         : Lavf56.40.101\n  Duration: 00:00:01.72, start: 0.000000, bitrate: 458 kb/s\n    Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 480x264 [SAR 1:1 DAR 20:11], 453 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)\n    Metadata:\n      handler_name    : VideoHandler\n      vendor_id       : [0][0][0][0]\nCodec AVOption vbr (Variable bit rate mode) specified for output file #0 (D:\\kek.mp4) has not been used for any stream. The most likely reason is either wrong type (e.g. a video option with no video streams) or that it is a private option of some encoder which was not actually used for any stream.\nStream mapping:\n  Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))\nPress [q] to stop, [?] for help\n[libx264 @ 000001cd5ac43100] width not divisible by 2 (101x101)\nError initializing output stream 0:0 -- Error while opening encoder for output stream #0:0 - maybe incorrect parameters such as bit_rate, rate, width or height\nConversion failed!"
```
While stacktrace is rather intimidating the "width not divisible by 2" suggests that `FFMpeg` has a thing for odd width and height. I use this simple hack to trick it and force it to convert my video
```ocaml
let newWidth =
    if videoInfo.PrimaryVideoStream.Height % 2 = 0 then
        videoInfo.PrimaryVideoStream.Height
    else videoInfo.PrimaryVideoStream.Height - 1
```
`videoInfo` here is a result of `FFProbe` work couple of paragraphs above.

FFmpegCore is capable of much more i.e. capturing screenshots, changing video thumbnail, etc but I'll leave it to research for a curious reader.