Merge branch 'master' into master

Former-commit-id: fc9db2c04a
This commit is contained in:
Malte Rosenbjerg 2021-08-11 00:43:03 +02:00 committed by GitHub
commit f7dfc6106c
11 changed files with 127 additions and 106 deletions

View file

@ -8,6 +8,7 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FFMpegCore.Extend;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {

View file

@ -63,6 +63,8 @@ public void Probe_Success()
Assert.AreEqual("LC", info.PrimaryAudioStream.Profile); Assert.AreEqual("LC", info.PrimaryAudioStream.Profile);
Assert.AreEqual(377351, info.PrimaryAudioStream.BitRate); Assert.AreEqual(377351, info.PrimaryAudioStream.BitRate);
Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz); Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz);
Assert.AreEqual("mp4a", info.PrimaryAudioStream.CodecTagString);
Assert.AreEqual("0x6134706d", info.PrimaryAudioStream.CodecTag);
Assert.AreEqual(1471810, info.PrimaryVideoStream!.BitRate); Assert.AreEqual(1471810, info.PrimaryVideoStream!.BitRate);
Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width); Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width);
@ -76,6 +78,8 @@ public void Probe_Success()
Assert.AreEqual("h264", info.PrimaryVideoStream.CodecName); Assert.AreEqual("h264", info.PrimaryVideoStream.CodecName);
Assert.AreEqual(8, info.PrimaryVideoStream.BitsPerRawSample); Assert.AreEqual(8, info.PrimaryVideoStream.BitsPerRawSample);
Assert.AreEqual("Main", info.PrimaryVideoStream.Profile); Assert.AreEqual("Main", info.PrimaryVideoStream.Profile);
Assert.AreEqual("avc1", info.PrimaryVideoStream.CodecTagString);
Assert.AreEqual("0x31637661", info.PrimaryVideoStream.CodecTag);
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]

View file

@ -655,7 +655,7 @@ public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
.WithAudioCodec(AudioCodec.Aac) .WithAudioCodec(AudioCodec.Aac)
.WithVideoCodec(VideoCodec.LibX264) .WithVideoCodec(VideoCodec.LibX264)
.WithSpeedPreset(Speed.VeryFast)) .WithSpeedPreset(Speed.VeryFast))
.CancellableThrough(cts.Token, 5000) .CancellableThrough(cts.Token, 8000)
.ProcessAsynchronously(false); .ProcessAsynchronously(false);
await Task.Delay(300); await Task.Delay(300);

View file

@ -1,27 +1,30 @@
using FFMpegCore.Pipes; using System.IO;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FFMpegCore.Pipes;
public class PcmAudioSampleWrapper : IAudioSample namespace FFMpegCore.Extend
{ {
//This could actually be short or int, but copies would be inefficient. public class PcmAudioSampleWrapper : IAudioSample
//Handling bytes lets the user decide on the conversion, and abstract the library
//from handling shorts, unsigned shorts, integers, unsigned integers and floats.
private readonly byte[] _sample;
public PcmAudioSampleWrapper(byte[] sample)
{ {
_sample = sample; //This could actually be short or int, but copies would be inefficient.
} //Handling bytes lets the user decide on the conversion, and abstract the library
//from handling shorts, unsigned shorts, integers, unsigned integers and floats.
private readonly byte[] _sample;
public void Serialize(Stream stream) public PcmAudioSampleWrapper(byte[] sample)
{ {
stream.Write(_sample, 0, _sample.Length); _sample = sample;
} }
public async Task SerializeAsync(Stream stream, CancellationToken token) public void Serialize(Stream stream)
{ {
await stream.WriteAsync(_sample, 0, _sample.Length, token); stream.Write(_sample, 0, _sample.Length);
}
public async Task SerializeAsync(Stream stream, CancellationToken token)
{
await stream.WriteAsync(_sample, 0, _sample.Length, token);
}
} }
} }

View file

@ -247,10 +247,10 @@ public static bool PosterWithAudio(string image, string audio, string output)
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image)); FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image));
return FFMpegArguments return FFMpegArguments
.FromFileInput(image) .FromFileInput(image, false, options => options
.Loop(1))
.AddFileInput(audio) .AddFileInput(audio)
.OutputToFile(output, true, options => options .OutputToFile(output, true, options => options
.Loop(1)
.WithVideoCodec(VideoCodec.LibX264) .WithVideoCodec(VideoCodec.LibX264)
.WithConstantRateFactor(21) .WithConstantRateFactor(21)
.WithAudioBitrate(AudioQuality.Normal) .WithAudioBitrate(AudioQuality.Normal)

View file

@ -6,9 +6,8 @@
<PackageProjectUrl>https://github.com/rosenbjerg/FFMpegCore</PackageProjectUrl> <PackageProjectUrl>https://github.com/rosenbjerg/FFMpegCore</PackageProjectUrl>
<Copyright></Copyright> <Copyright></Copyright>
<Description>A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications</Description> <Description>A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications</Description>
<Version>3.0.0.0</Version> <AssemblyVersion>4.0.0.0</AssemblyVersion>
<AssemblyVersion>3.0.0.0</AssemblyVersion> <PackageReadmeFile>README.md</PackageReadmeFile>
<FileVersion>3.0.0.0</FileVersion>
<PackageReleaseNotes>- Cancellation token support (thanks patagonaa) <PackageReleaseNotes>- Cancellation token support (thanks patagonaa)
- Support for setting stdout and stderr encoding for ffprobe (thanks CepheiSigma) - Support for setting stdout and stderr encoding for ffprobe (thanks CepheiSigma)
- Improved ffprobe exceptions</PackageReleaseNotes> - Improved ffprobe exceptions</PackageReleaseNotes>
@ -27,6 +26,7 @@
<Content Include="FFMPEG\bin\**\*"> <Content Include="FFMPEG\bin\**\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -41,6 +41,12 @@ public class FFProbeStream : ITagsContainer
[JsonPropertyName("codec_long_name")] [JsonPropertyName("codec_long_name")]
public string CodecLongName { get; set; } = null!; public string CodecLongName { get; set; } = null!;
[JsonPropertyName("codec_tag")]
public string CodecTag { get; set; } = null!;
[JsonPropertyName("codec_tag_string")]
public string CodecTagString { get; set; } = null!;
[JsonPropertyName("display_aspect_ratio")] [JsonPropertyName("display_aspect_ratio")]
public string DisplayAspectRatio { get; set; } = null!; public string DisplayAspectRatio { get; set; } = null!;

View file

@ -56,6 +56,8 @@ private VideoStream ParseVideoStream(FFProbeStream stream)
BitsPerRawSample = !string.IsNullOrEmpty(stream.BitsPerRawSample) ? MediaAnalysisUtils.ParseIntInvariant(stream.BitsPerRawSample) : default, BitsPerRawSample = !string.IsNullOrEmpty(stream.BitsPerRawSample) ? MediaAnalysisUtils.ParseIntInvariant(stream.BitsPerRawSample) : default,
CodecName = stream.CodecName, CodecName = stream.CodecName,
CodecLongName = stream.CodecLongName, CodecLongName = stream.CodecLongName,
CodecTag = stream.CodecTag,
CodecTagString = stream.CodecTagString,
DisplayAspectRatio = MediaAnalysisUtils.ParseRatioInt(stream.DisplayAspectRatio, ':'), DisplayAspectRatio = MediaAnalysisUtils.ParseRatioInt(stream.DisplayAspectRatio, ':'),
Duration = MediaAnalysisUtils.ParseDuration(stream), Duration = MediaAnalysisUtils.ParseDuration(stream),
FrameRate = MediaAnalysisUtils.DivideRatio(MediaAnalysisUtils.ParseRatioDouble(stream.FrameRate, '/')), FrameRate = MediaAnalysisUtils.DivideRatio(MediaAnalysisUtils.ParseRatioDouble(stream.FrameRate, '/')),
@ -77,6 +79,8 @@ private AudioStream ParseAudioStream(FFProbeStream stream)
BitRate = !string.IsNullOrEmpty(stream.BitRate) ? MediaAnalysisUtils.ParseIntInvariant(stream.BitRate) : default, BitRate = !string.IsNullOrEmpty(stream.BitRate) ? MediaAnalysisUtils.ParseIntInvariant(stream.BitRate) : default,
CodecName = stream.CodecName, CodecName = stream.CodecName,
CodecLongName = stream.CodecLongName, CodecLongName = stream.CodecLongName,
CodecTag = stream.CodecTag,
CodecTagString = stream.CodecTagString,
Channels = stream.Channels ?? default, Channels = stream.Channels ?? default,
ChannelLayout = stream.ChannelLayout, ChannelLayout = stream.ChannelLayout,
Duration = MediaAnalysisUtils.ParseDuration(stream), Duration = MediaAnalysisUtils.ParseDuration(stream),

View file

@ -10,6 +10,8 @@ public class MediaStream
public int Index { get; internal set; } public int Index { get; internal set; }
public string CodecName { get; internal set; } = null!; public string CodecName { get; internal set; } = null!;
public string CodecLongName { get; internal set; } = null!; public string CodecLongName { get; internal set; } = null!;
public string CodecTagString { get; set; } = null!;
public string CodecTag { get; set; } = null!;
public int BitRate { get; internal set; } public int BitRate { get; internal set; }
public TimeSpan Duration { get; internal set; } public TimeSpan Duration { get; internal set; }
public string? Language { get; internal set; } public string? Language { get; internal set; }

View file

@ -8,27 +8,20 @@ namespace FFMpegCore
public static class GlobalFFOptions public static class GlobalFFOptions
{ {
private static readonly string ConfigFile = "ffmpeg.config.json"; private static readonly string ConfigFile = "ffmpeg.config.json";
private static FFOptions? _current;
public static FFOptions Current { get; private set; } public static FFOptions Current
static GlobalFFOptions()
{ {
if (File.Exists(ConfigFile)) get { return _current ??= LoadFFOptions(); }
{
Current = JsonSerializer.Deserialize<FFOptions>(File.ReadAllText(ConfigFile))!;
}
else
{
Current = new FFOptions();
}
} }
public static void Configure(Action<FFOptions> optionsAction) public static void Configure(Action<FFOptions> optionsAction)
{ {
optionsAction?.Invoke(Current); optionsAction?.Invoke(Current);
} }
public static void Configure(FFOptions ffOptions) public static void Configure(FFOptions ffOptions)
{ {
Current = ffOptions ?? throw new ArgumentNullException(nameof(ffOptions)); _current = ffOptions ?? throw new ArgumentNullException(nameof(ffOptions));
} }
@ -48,5 +41,17 @@ private static string GetFFBinaryPath(string name, FFOptions ffOptions)
return Path.Combine(ffOptions.BinaryFolder, ffName); return Path.Combine(ffOptions.BinaryFolder, ffName);
} }
private static FFOptions LoadFFOptions()
{
if (File.Exists(ConfigFile))
{
return JsonSerializer.Deserialize<FFOptions>(File.ReadAllText(ConfigFile))!;
}
else
{
return new FFOptions();
}
}
} }
} }

136
README.md
View file

@ -1,37 +1,29 @@
# FFMpegCore # [FFMpegCore](https://www.nuget.org/packages/FFMpegCore/)
[![CI](https://github.com/rosenbjerg/FFMpegCore/workflows/CI/badge.svg)](https://github.com/rosenbjerg/FFMpegCore/actions?query=workflow%3ACI)
[![NuGet Badge](https://buildstats.info/nuget/FFMpegCore)](https://www.nuget.org/packages/FFMpegCore/) [![NuGet Badge](https://buildstats.info/nuget/FFMpegCore)](https://www.nuget.org/packages/FFMpegCore/)
[![GitHub issues](https://img.shields.io/github/issues/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/issues) [![GitHub issues](https://img.shields.io/github/issues/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/issues)
[![GitHub stars](https://img.shields.io/github/stars/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/stargazers) [![GitHub stars](https://img.shields.io/github/stars/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/stargazers)
[![GitHub](https://img.shields.io/github/license/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/blob/master/LICENSE) [![GitHub](https://img.shields.io/github/license/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/blob/master/LICENSE)
[![CI](https://github.com/rosenbjerg/FFMpegCore/workflows/CI/badge.svg)](https://github.com/rosenbjerg/FFMpegCore/actions?query=workflow%3ACI)
# Setup A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications. Supports both synchronous and asynchronous calls
#### NuGet:
```
Install-Package FFMpegCore
```
A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your C# applications. Support both synchronous and asynchronous use
# API # API
## FFProbe ## FFProbe
FFProbe is used to gather media information: Use FFProbe to analyze media files:
```csharp ```csharp
var mediaInfo = FFProbe.Analyse(inputPath); var mediaInfo = await FFProbe.AnalyseAsync(inputPath);
``` ```
or or
```csharp ```csharp
var mediaInfo = await FFProbe.AnalyseAsync(inputPath); var mediaInfo = FFProbe.Analyse(inputPath);
``` ```
## FFMpeg ## FFMpeg
FFMpeg is used for converting your media files to web ready formats. Use FFMpeg to convert your media files.
Easily build your FFMpeg arguments using the fluent argument builder: Easily build your FFMpeg arguments using the fluent argument builder:
Convert input file to h264/aac scaled to 720p w/ faststart, for web playback Convert input file to h264/aac scaled to 720p w/ faststart, for web playback
@ -49,15 +41,6 @@ FFMpegArguments
.ProcessSynchronously(); .ProcessSynchronously();
``` ```
Easily capture screens from your videos:
```csharp
// process the snapshot in-memory and use the Bitmap directly
var bitmap = FFMpeg.Snapshot(inputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
// or persists the image on the drive
FFMpeg.Snapshot(inputPath, outputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
```
Convert to and/or from streams Convert to and/or from streams
```csharp ```csharp
await FFMpegArguments await FFMpegArguments
@ -68,7 +51,19 @@ await FFMpegArguments
.ProcessAsynchronously(); .ProcessAsynchronously();
``` ```
Join video parts into one single file: ## Helper methods
The provided helper methods makes it simple to perform common operations.
### Easily capture snapshots from a video file:
```csharp
// process the snapshot in-memory and use the Bitmap directly
var bitmap = FFMpeg.Snapshot(inputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
// or persists the image on the drive
FFMpeg.Snapshot(inputPath, outputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
```
### Join video parts into one single file:
```csharp ```csharp
FFMpeg.Join(@"..\joined_video.mp4", FFMpeg.Join(@"..\joined_video.mp4",
@"..\part1.mp4", @"..\part1.mp4",
@ -77,7 +72,7 @@ FFMpeg.Join(@"..\joined_video.mp4",
); );
``` ```
Join images into a video: ### Join images into a video:
```csharp ```csharp
FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1,
ImageInfo.FromPath(@"..\1.png"), ImageInfo.FromPath(@"..\1.png"),
@ -86,22 +81,22 @@ FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1,
); );
``` ```
Mute videos: ### Mute the audio of a video file:
```csharp ```csharp
FFMpeg.Mute(inputPath, outputPath); FFMpeg.Mute(inputPath, outputPath);
``` ```
Save audio track from video: ### Extract the audio track from a video file:
```csharp ```csharp
FFMpeg.ExtractAudio(inputPath, outputPath); FFMpeg.ExtractAudio(inputPath, outputPath);
``` ```
Add or replace audio track on video: ### Add or replace the audio track of a video file:
```csharp ```csharp
FFMpeg.ReplaceAudio(inputPath, inputAudioPath, outputPath); FFMpeg.ReplaceAudio(inputPath, inputAudioPath, outputPath);
``` ```
Add poster image to audio file (good for youtube videos): ### Combine an image with audio file, for youtube or similar platforms
```csharp ```csharp
FFMpeg.PosterWithAudio(inputPath, inputAudioPath, outputPath); FFMpeg.PosterWithAudio(inputPath, inputAudioPath, outputPath);
// or // or
@ -111,26 +106,27 @@ image.AddAudio(inputAudioPath, outputPath);
Other available arguments could be found in `FFMpegCore.Arguments` namespace. Other available arguments could be found in `FFMpegCore.Arguments` namespace.
### Input piping ## Input piping
With input piping it is possible to write video frames directly from program memory without saving them to jpeg or png and then passing path to input of ffmpeg. This feature also allows us to convert video on-the-fly while frames are being generated or received. With input piping it is possible to write video frames directly from program memory without saving them to jpeg or png and then passing path to input of ffmpeg. This feature also allows for converting video on-the-fly while frames are being generated or received.
The `IPipeSource` interface is used as the source of data. It could be represented as encoded video stream or raw frames stream. Currently, the `IPipeSource` interface has single implementation, `RawVideoPipeSource` that is used for raw stream encoding. An object implementing the `IPipeSource` interface is used as the source of data. Currently, the `IPipeSource` interface has two implementations; `StreamPipeSource` for streams, and `RawVideoPipeSource` for raw video frames.
For example: ### Working with raw video frames
Method that is generating bitmap frames: Method for generating bitmap frames:
```csharp ```csharp
IEnumerable<IVideoFrame> CreateFrames(int count) IEnumerable<IVideoFrame> CreateFrames(int count)
{ {
for(int i = 0; i < count; i++) for(int i = 0; i < count; i++)
{ {
yield return GetNextFrame(); //method of generating new frames yield return GetNextFrame(); //method that generates of receives the next frame
} }
} }
``` ```
Then create `ArgumentsContainer` with `InputPipeArgument`
Then create a `RawVideoPipeSource` that utilises your video frame source
```csharp ```csharp
var videoFramesSource = new RawVideoPipeSource(CreateFrames(64)) //pass IEnumerable<IVideoFrame> or IEnumerator<IVideoFrame> to constructor of RawVideoPipeSource var videoFramesSource = new RawVideoPipeSource(CreateFrames(64))
{ {
FrameRate = 30 //set source frame rate FrameRate = 30 //set source frame rate
}; };
@ -141,51 +137,43 @@ await FFMpegArguments
.ProcessAsynchronously(); .ProcessAsynchronously();
``` ```
if you want to use `System.Drawing.Bitmap` as `IVideoFrame`, there is a `BitmapVideoFrameWrapper` wrapper class. If you want to use `System.Drawing.Bitmap`s as `IVideoFrame`s, a `BitmapVideoFrameWrapper` wrapper class is provided.
## Binaries # Binaries
## Installation
If you prefer to manually download them, visit [ffbinaries](https://ffbinaries.com/downloads) or [zeranoe Windows builds](https://ffmpeg.zeranoe.com/builds/). If you prefer to manually download them, visit [ffbinaries](https://ffbinaries.com/downloads) or [zeranoe Windows builds](https://ffmpeg.zeranoe.com/builds/).
#### Windows ### Windows (using choco)
command: `choco install ffmpeg -y`
command: `choco install ffmpeg -Y`
location: `C:\ProgramData\chocolatey\lib\ffmpeg\tools\ffmpeg\bin` location: `C:\ProgramData\chocolatey\lib\ffmpeg\tools\ffmpeg\bin`
#### Mac OSX ### Mac OSX
command: `brew install ffmpeg mono-libgdiplus` command: `brew install ffmpeg mono-libgdiplus`
location: `/usr/local/bin` location: `/usr/local/bin`
#### Ubuntu ### Ubuntu
command: `sudo apt-get install -y ffmpeg libgdiplus` command: `sudo apt-get install -y ffmpeg libgdiplus`
location: `/usr/bin` location: `/usr/bin`
## Path Configuration ## Path Configuration
#### Behavior ### Option 1
If you wish to support multiple client processor architectures, you can do so by creating a folder `x64` and `x86` in the `root` directory.
Both folders should contain the binaries (`ffmpeg.exe` and `ffprobe.exe`) for build for the respective architectures.
By doing so, the library will attempt to use either `/root/{ARCH}/(ffmpeg|ffprobe).exe`.
If these folders are not defined, it will try to find the binaries in `/root/(ffmpeg|ffprobe.exe)`
#### Option 1
The default value of an empty string (expecting ffmpeg to be found through PATH) can be overwritten via the `FFOptions` class: The default value of an empty string (expecting ffmpeg to be found through PATH) can be overwritten via the `FFOptions` class:
```c# ```csharp
// setting global options // setting global options
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "./bin", TemporaryFilesFolder = "/tmp" }); GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "./bin", TemporaryFilesFolder = "/tmp" });
// or // or
GlobalFFOptions.Configure(options => options.BinaryFolder = "./bin"); GlobalFFOptions.Configure(options => options.BinaryFolder = "./bin");
// on some systems the absolute path may be required, in which case // on some systems the absolute path may be required, in which case
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = Server.MapPath("./bin"), TemporaryFilesFolder = Server.MapPath("/tmp") }); GlobalFFOptions.Configure(new FFOptions { BinaryFolder = Server.MapPath("./bin"), TemporaryFilesFolder = Server.MapPath("/tmp") });
@ -196,9 +184,9 @@ await FFMpegArguments
.ProcessAsynchronously(true, new FFOptions { BinaryFolder = "./bin", TemporaryFilesFolder = "/tmp" }); .ProcessAsynchronously(true, new FFOptions { BinaryFolder = "./bin", TemporaryFilesFolder = "/tmp" });
``` ```
#### Option 2 ### Option 2
The root and temp directory for the ffmpeg binaries can be configured via the `ffmpeg.config.json` file. The root and temp directory for the ffmpeg binaries can be configured via the `ffmpeg.config.json` file, which will be read on first use only.
```json ```json
{ {
@ -207,22 +195,30 @@ The root and temp directory for the ffmpeg binaries can be configured via the `f
} }
``` ```
### Supporting both 32 and 64 bit processes
If you wish to support multiple client processor architectures, you can do so by creating a folder `x64` and `x86` in the `root` directory.
Both folders should contain the binaries (`ffmpeg.exe` and `ffprobe.exe`) for build for the respective architectures.
By doing so, the library will attempt to use either `/root/{ARCH}/(ffmpeg|ffprobe).exe`.
If these folders are not defined, it will try to find the binaries in `/root/(ffmpeg|ffprobe.exe)`.
(`.exe` is only appended on Windows)
# Compatibility # Compatibility
Some versions of FFMPEG might not have the same argument schema. The lib has been tested with version `3.3` to `4.2` Older versions of ffmpeg might not support all ffmpeg arguments available through this library. The library has been tested with version `3.3` to `4.2`
## Contributors ## Code contributors
<a href="https://github.com/rosenbjerg/ffmpegcore/graphs/contributors">
<img src="https://contrib.rocks/image?repo=rosenbjerg/ffmpegcore" />
</a>
<a href="https://github.com/vladjerca"><img src="https://avatars.githubusercontent.com/u/6339681?v=4" title="vladjerca" width="80" height="80"></a> ## Non-code contributors
<a href="https://github.com/max619"><img src="https://avatars.githubusercontent.com/u/26447324?v=4" title="max619" width="80" height="80"></a>
<a href="https://github.com/kyriakosio"><img src="https://avatars3.githubusercontent.com/u/6959989?v=4" title="kyriakosio" width="80" height="80"></a>
<a href="https://github.com/winchesterag"><img src="https://avatars3.githubusercontent.com/u/47878681?v=4" title="winchesterag" width="80" height="80"></a>
<a href="https://github.com/devlev"><img src="https://avatars3.githubusercontent.com/u/2109995?v=4" title="devlev" width="80" height="80"></a>
<a href="https://github.com/tugrulelmas"><img src="https://avatars3.githubusercontent.com/u/3829187?v=4" title="tugrulelmas" width="80" height="80"></a>
<a href="https://github.com/rosenbjerg"><img src="https://avatars3.githubusercontent.com/u/11181960?v=4" title="rosenbjerg" width="80" height="80"></a>
<a href="https://github.com/WeihanLi"><img src="https://avatars3.githubusercontent.com/u/7604648?v=4" title="weihanli" width="80" height="80"></a>
<a href="https://github.com/tiesont"><img src="https://avatars3.githubusercontent.com/u/420293?v=4" title="tiesont" width="80" height="80"></a> <a href="https://github.com/tiesont"><img src="https://avatars3.githubusercontent.com/u/420293?v=4" title="tiesont" width="80" height="80"></a>
### License ### License
Copyright © 2021 Copyright © 2021