mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
commit
732e3a0772
28 changed files with 418 additions and 500 deletions
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
|
@ -2,8 +2,8 @@ name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches:
|
||||||
- release
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
@ -11,15 +11,19 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 7
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [windows-latest, ubuntu-latest]
|
||||||
|
timeout-minutes: 6
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- name: Checkout
|
||||||
- name: Prepare FFMpeg
|
uses: actions/checkout@v1
|
||||||
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
- name: Prepare .NET
|
||||||
- name: Setup .NET Core
|
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 3.1.101
|
dotnet-version: '5.0.x'
|
||||||
|
- name: Prepare FFMpeg
|
||||||
|
uses: FedericoCarboni/setup-ffmpeg@v1-beta
|
||||||
- name: Test with dotnet
|
- name: Test with dotnet
|
||||||
run: dotnet test --logger GitHubActions
|
run: dotnet test --logger GitHubActions
|
||||||
|
|
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
|
@ -7,13 +7,14 @@ jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- name: Checkout
|
||||||
- name: Setup .NET Core
|
uses: actions/checkout@v1
|
||||||
|
- name: Prepare .NET
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 3.1
|
dotnet-version: '5.0.x'
|
||||||
- name: Build solution
|
- name: Build solution
|
||||||
run: dotnet build --output build
|
run: dotnet build --output build -c Release
|
||||||
- name: Publish NuGet package
|
- name: Publish NuGet package
|
||||||
run: dotnet nuget push "build/*.nupkg" --skip-duplicate --source nuget.org --api-key ${{ secrets.NUGET_TOKEN }}
|
run: dotnet nuget push "build/*.nupkg" --source nuget.org --api-key ${{ secrets.NUGET_TOKEN }}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class ArgumentBuilderTest : BaseTest
|
public class ArgumentBuilderTest
|
||||||
{
|
{
|
||||||
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4"};
|
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4"};
|
||||||
|
|
||||||
|
|
|
@ -3,80 +3,72 @@
|
||||||
using FFMpegCore.Test.Resources;
|
using FFMpegCore.Test.Resources;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class AudioTest : BaseTest
|
public class AudioTest
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Audio_Remove()
|
public void Audio_Remove()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
using var outputFile = new TemporaryFile("out.mp4");
|
||||||
|
|
||||||
try
|
FFMpeg.Mute(TestResources.Mp4Video, outputFile);
|
||||||
{
|
var analysis = FFProbe.Analyse(outputFile);
|
||||||
FFMpeg.Mute(Input.FullName, output);
|
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(analysis.VideoStreams.Any());
|
||||||
}
|
Assert.IsTrue(!analysis.AudioStreams.Any());
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output)) File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Audio_Save()
|
public void Audio_Save()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(AudioType.Mp3);
|
using var outputFile = new TemporaryFile("out.mp3");
|
||||||
|
|
||||||
try
|
FFMpeg.ExtractAudio(TestResources.Mp4Video, outputFile);
|
||||||
{
|
var analysis = FFProbe.Analyse(outputFile);
|
||||||
FFMpeg.ExtractAudio(Input.FullName, output);
|
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(!analysis.VideoStreams.Any());
|
||||||
|
Assert.IsTrue(analysis.AudioStreams.Any());
|
||||||
}
|
}
|
||||||
finally
|
[TestMethod]
|
||||||
|
public async Task Audio_FromRaw()
|
||||||
{
|
{
|
||||||
if (File.Exists(output)) File.Delete(output);
|
await using var file = File.Open(TestResources.RawAudio, FileMode.Open);
|
||||||
}
|
var memoryStream = new MemoryStream();
|
||||||
|
await FFMpegArguments
|
||||||
|
.FromPipeInput(new StreamPipeSource(file), options => options.ForceFormat("s16le"))
|
||||||
|
.OutputToPipe(new StreamPipeSink(memoryStream), options => options.ForceFormat("mp3"))
|
||||||
|
.ProcessAsynchronously();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Audio_Add()
|
public void Audio_Add()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
using var outputFile = new TemporaryFile("out.mp4");
|
||||||
try
|
|
||||||
{
|
var success = FFMpeg.ReplaceAudio(TestResources.Mp4WithoutAudio, TestResources.Mp3Audio, outputFile);
|
||||||
var success = FFMpeg.ReplaceAudio(VideoLibrary.LocalVideoNoAudio.FullName, VideoLibrary.LocalAudio.FullName, output);
|
var videoAnalysis = FFProbe.Analyse(TestResources.Mp4WithoutAudio);
|
||||||
|
var audioAnalysis = FFProbe.Analyse(TestResources.Mp3Audio);
|
||||||
|
var outputAnalysis = FFProbe.Analyse(outputFile);
|
||||||
|
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
var audioAnalysis = FFProbe.Analyse(VideoLibrary.LocalVideoNoAudio.FullName);
|
|
||||||
var videoAnalysis = FFProbe.Analyse(VideoLibrary.LocalAudio.FullName);
|
|
||||||
var outputAnalysis = FFProbe.Analyse(output);
|
|
||||||
Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
|
Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output)) File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Image_AddAudio()
|
public void Image_AddAudio()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
using var outputFile = new TemporaryFile("out.mp4");
|
||||||
|
FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile);
|
||||||
try
|
var analysis = FFProbe.Analyse(TestResources.Mp3Audio);
|
||||||
{
|
|
||||||
FFMpeg.PosterWithAudio(VideoLibrary.LocalCover.FullName, VideoLibrary.LocalAudio.FullName, output);
|
|
||||||
var analysis = FFProbe.Analyse(VideoLibrary.LocalAudio.FullName);
|
|
||||||
Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
|
Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output)) File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
using FFMpegCore.Test.Resources;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
|
||||||
{
|
|
||||||
public class BaseTest
|
|
||||||
{
|
|
||||||
protected FileInfo Input;
|
|
||||||
|
|
||||||
public BaseTest()
|
|
||||||
{
|
|
||||||
Input = VideoLibrary.LocalVideo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
|
@ -27,6 +27,9 @@
|
||||||
<None Update="Resources\input_video_only_3sec.mp4">
|
<None Update="Resources\input_video_only_3sec.mp4">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Resources\audio.raw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -36,8 +39,8 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.1.1" />
|
<PackageReference Include="GitHubActionsTestLogger" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -11,16 +11,26 @@ public class FFProbeTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Probe_TooLongOutput()
|
public void Probe_TooLongOutput()
|
||||||
{
|
{
|
||||||
Assert.ThrowsException<System.Text.Json.JsonException>(() => FFProbe.Analyse(VideoLibrary.LocalVideo.FullName, 5));
|
Assert.ThrowsException<System.Text.Json.JsonException>(() => FFProbe.Analyse(TestResources.Mp4Video, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Audio_FromStream_Duration()
|
||||||
|
{
|
||||||
|
var fileAnalysis = await FFProbe.AnalyseAsync(TestResources.WebmVideo);
|
||||||
|
await using var inputStream = File.OpenRead(TestResources.WebmVideo);
|
||||||
|
var streamAnalysis = await FFProbe.AnalyseAsync(inputStream);
|
||||||
|
Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Probe_Success()
|
public void Probe_Success()
|
||||||
{
|
{
|
||||||
var info = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
var info = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
Assert.AreEqual(3, info.Duration.Seconds);
|
Assert.AreEqual(3, info.Duration.Seconds);
|
||||||
Assert.AreEqual(".mp4", info.Extension);
|
Assert.AreEqual(".mp4", info.Extension);
|
||||||
Assert.AreEqual(VideoLibrary.LocalVideo.FullName, info.Path);
|
Assert.AreEqual(TestResources.Mp4Video, info.Path);
|
||||||
|
|
||||||
Assert.AreEqual("5.1", info.PrimaryAudioStream.ChannelLayout);
|
Assert.AreEqual("5.1", info.PrimaryAudioStream.ChannelLayout);
|
||||||
Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
|
Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
|
||||||
|
@ -47,14 +57,14 @@ public void Probe_Success()
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public async Task Probe_Async_Success()
|
public async Task Probe_Async_Success()
|
||||||
{
|
{
|
||||||
var info = await FFProbe.AnalyseAsync(VideoLibrary.LocalVideo.FullName);
|
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||||
Assert.AreEqual(3, info.Duration.Seconds);
|
Assert.AreEqual(3, info.Duration.Seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Probe_Success_FromStream()
|
public void Probe_Success_FromStream()
|
||||||
{
|
{
|
||||||
using var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName);
|
using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||||
var info = FFProbe.Analyse(stream);
|
var info = FFProbe.Analyse(stream);
|
||||||
Assert.AreEqual(3, info.Duration.Seconds);
|
Assert.AreEqual(3, info.Duration.Seconds);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +72,7 @@ public void Probe_Success_FromStream()
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public async Task Probe_Success_FromStream_Async()
|
public async Task Probe_Success_FromStream_Async()
|
||||||
{
|
{
|
||||||
await using var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName);
|
await using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||||
var info = await FFProbe.AnalyseAsync(stream);
|
var info = await FFProbe.AnalyseAsync(stream);
|
||||||
Assert.AreEqual(3, info.Duration.Seconds);
|
Assert.AreEqual(3, info.Duration.Seconds);
|
||||||
}
|
}
|
||||||
|
|
28
FFMpegCore.Test/Resources/TestResources.cs
Normal file
28
FFMpegCore.Test/Resources/TestResources.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Test.Resources
|
||||||
|
{
|
||||||
|
public enum AudioType
|
||||||
|
{
|
||||||
|
Mp3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ImageType
|
||||||
|
{
|
||||||
|
Png
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestResources
|
||||||
|
{
|
||||||
|
public static readonly string Mp4Video = "./Resources/input_3sec.mp4";
|
||||||
|
public static readonly string WebmVideo = "./Resources/input_3sec.webm";
|
||||||
|
public static readonly string Mp4WithoutVideo = "./Resources/input_audio_only_10sec.mp4";
|
||||||
|
public static readonly string Mp4WithoutAudio = "./Resources/input_video_only_3sec.mp4";
|
||||||
|
public static readonly string RawAudio = "./Resources/audio.raw";
|
||||||
|
public static readonly string Mp3Audio = "./Resources/audio.mp3";
|
||||||
|
public static readonly string PngImage = "./Resources/cover.png";
|
||||||
|
public static readonly string ImageCollection = "./Resources/images";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,51 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using FFMpegCore.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Test.Resources
|
|
||||||
{
|
|
||||||
public enum AudioType
|
|
||||||
{
|
|
||||||
Mp3
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ImageType
|
|
||||||
{
|
|
||||||
Png
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class VideoLibrary
|
|
||||||
{
|
|
||||||
public static readonly FileInfo LocalVideo = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input_3sec.mp4");
|
|
||||||
public static readonly FileInfo LocalVideoWebm = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input_3sec.webm");
|
|
||||||
public static readonly FileInfo LocalVideoAudioOnly = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input_audio_only_10sec.mp4");
|
|
||||||
public static readonly FileInfo LocalVideoNoAudio = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input_video_only_3sec.mp4");
|
|
||||||
public static readonly FileInfo LocalAudio = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}audio.mp3");
|
|
||||||
public static readonly FileInfo LocalCover = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}cover.png");
|
|
||||||
public static readonly FileInfo ImageDirectory = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images");
|
|
||||||
public static readonly FileInfo ImageJoinOutput = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images{Path.DirectorySeparatorChar}output.mp4");
|
|
||||||
|
|
||||||
public static string OutputLocation(this FileInfo file, ContainerFormat type)
|
|
||||||
{
|
|
||||||
return OutputLocation(file, type.Extension, "_converted");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string OutputLocation(this FileInfo file, AudioType type)
|
|
||||||
{
|
|
||||||
return OutputLocation(file, type.ToString(), "_audio");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string OutputLocation(this FileInfo file, ImageType type)
|
|
||||||
{
|
|
||||||
return OutputLocation(file, type.ToString(), "_screenshot");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string OutputLocation(this FileInfo file, string type, string keyword)
|
|
||||||
{
|
|
||||||
string originalLocation = file.Directory.FullName,
|
|
||||||
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToLowerInvariant());
|
|
||||||
|
|
||||||
return $"{originalLocation}{Path.DirectorySeparatorChar}{Guid.NewGuid()}_{outputFile}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
BIN
FFMpegCore.Test/Resources/audio.raw
Normal file
BIN
FFMpegCore.Test/Resources/audio.raw
Normal file
Binary file not shown.
After Width: | Height: | Size: 375 KiB |
22
FFMpegCore.Test/TemporaryFile.cs
Normal file
22
FFMpegCore.Test/TemporaryFile.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Test
|
||||||
|
{
|
||||||
|
public class TemporaryFile : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _path;
|
||||||
|
|
||||||
|
public TemporaryFile(string filename)
|
||||||
|
{
|
||||||
|
_path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}-{filename}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator string(TemporaryFile temporaryFile) => temporaryFile._path;
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (File.Exists(_path))
|
||||||
|
File.Delete(_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,19 +15,17 @@
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class VideoTest : BaseTest
|
public class VideoTest
|
||||||
{
|
{
|
||||||
public bool Convert(ContainerFormat type, bool multithreaded = false, VideoSize size = VideoSize.Original)
|
public bool Convert(ContainerFormat type, bool multithreaded = false, VideoSize size = VideoSize.Original)
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(type);
|
using var outputFile = new TemporaryFile($"out{type.Extension}");
|
||||||
|
|
||||||
try
|
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
{
|
FFMpeg.Convert(input, outputFile, type, size: size, multithreaded: multithreaded);
|
||||||
var input = FFProbe.Analyse(Input.FullName);
|
var outputVideo = FFProbe.Analyse(outputFile);
|
||||||
FFMpeg.Convert(input, output, type, size: size, multithreaded: multithreaded);
|
|
||||||
var outputVideo = FFProbe.Analyse(output);
|
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
||||||
if (size == VideoSize.Original)
|
if (size == VideoSize.Original)
|
||||||
{
|
{
|
||||||
|
@ -40,7 +38,8 @@ public bool Convert(ContainerFormat type, bool multithreaded = false, VideoSize
|
||||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, (int)size);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, (int)size);
|
||||||
}
|
}
|
||||||
return File.Exists(output) &&
|
|
||||||
|
return File.Exists(outputFile) &&
|
||||||
outputVideo.Duration == input.Duration &&
|
outputVideo.Duration == input.Duration &&
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
|
@ -56,24 +55,16 @@ public bool Convert(ContainerFormat type, bool multithreaded = false, VideoSize
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ConvertFromStreamPipe(ContainerFormat type, params IArgument[] arguments)
|
private void ConvertFromStreamPipe(ContainerFormat type, params IArgument[] arguments)
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(type);
|
using var outputFile = new TemporaryFile($"out{type.Extension}");
|
||||||
|
|
||||||
try
|
var input = FFProbe.Analyse(TestResources.WebmVideo);
|
||||||
{
|
|
||||||
var input = FFProbe.Analyse(VideoLibrary.LocalVideoWebm.FullName);
|
|
||||||
using var inputStream = File.OpenRead(input.Path);
|
using var inputStream = File.OpenRead(input.Path);
|
||||||
var processor = FFMpegArguments
|
var processor = FFMpegArguments
|
||||||
.FromPipeInput(new StreamPipeSource(inputStream))
|
.FromPipeInput(new StreamPipeSource(inputStream))
|
||||||
.OutputToFile(output, false, opt =>
|
.OutputToFile(outputFile, false, opt =>
|
||||||
{
|
{
|
||||||
foreach (var arg in arguments)
|
foreach (var arg in arguments)
|
||||||
opt.WithArgument(arg);
|
opt.WithArgument(arg);
|
||||||
|
@ -83,10 +74,10 @@ private void ConvertFromStreamPipe(ContainerFormat type, params IArgument[] argu
|
||||||
|
|
||||||
var success = processor.ProcessSynchronously();
|
var success = processor.ProcessSynchronously();
|
||||||
|
|
||||||
var outputVideo = FFProbe.Analyse(output);
|
var outputVideo = FFProbe.Analyse(outputFile);
|
||||||
|
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
|
Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
|
||||||
|
|
||||||
if (scaling?.Size == null)
|
if (scaling?.Size == null)
|
||||||
|
@ -110,18 +101,12 @@ private void ConvertFromStreamPipe(ContainerFormat type, params IArgument[] argu
|
||||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ConvertToStreamPipe(params IArgument[] arguments)
|
private void ConvertToStreamPipe(params IArgument[] arguments)
|
||||||
{
|
{
|
||||||
using var ms = new MemoryStream();
|
using var ms = new MemoryStream();
|
||||||
var processor = FFMpegArguments
|
var processor = FFMpegArguments
|
||||||
.FromFileInput(VideoLibrary.LocalVideo)
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToPipe(new StreamPipeSink(ms), opt =>
|
.OutputToPipe(new StreamPipeSink(ms), opt =>
|
||||||
{
|
{
|
||||||
foreach (var arg in arguments)
|
foreach (var arg in arguments)
|
||||||
|
@ -135,7 +120,7 @@ private void ConvertToStreamPipe(params IArgument[] arguments)
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
var outputVideo = FFProbe.Analyse(ms);
|
var outputVideo = FFProbe.Analyse(ms);
|
||||||
|
|
||||||
var input = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
// Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
|
// Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
|
||||||
|
|
||||||
if (scaling?.Size == null)
|
if (scaling?.Size == null)
|
||||||
|
@ -162,15 +147,13 @@ private void ConvertToStreamPipe(params IArgument[] arguments)
|
||||||
|
|
||||||
public void Convert(ContainerFormat type, Action<IMediaAnalysis> validationMethod, params IArgument[] arguments)
|
public void Convert(ContainerFormat type, Action<IMediaAnalysis> validationMethod, params IArgument[] arguments)
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(type);
|
using var outputFile = new TemporaryFile($"out{type.Extension}");
|
||||||
|
|
||||||
try
|
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
{
|
|
||||||
var input = FFProbe.Analyse(Input.FullName);
|
|
||||||
|
|
||||||
var processor = FFMpegArguments
|
var processor = FFMpegArguments
|
||||||
.FromFileInput(VideoLibrary.LocalVideo)
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToFile(output, false, opt =>
|
.OutputToFile(outputFile, false, opt =>
|
||||||
{
|
{
|
||||||
foreach (var arg in arguments)
|
foreach (var arg in arguments)
|
||||||
opt.WithArgument(arg);
|
opt.WithArgument(arg);
|
||||||
|
@ -179,9 +162,9 @@ public void Convert(ContainerFormat type, Action<IMediaAnalysis> validationMetho
|
||||||
var scaling = arguments.OfType<ScaleArgument>().FirstOrDefault();
|
var scaling = arguments.OfType<ScaleArgument>().FirstOrDefault();
|
||||||
processor.ProcessSynchronously();
|
processor.ProcessSynchronously();
|
||||||
|
|
||||||
var outputVideo = FFProbe.Analyse(output);
|
var outputVideo = FFProbe.Analyse(outputFile);
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
||||||
validationMethod?.Invoke(outputVideo);
|
validationMethod?.Invoke(outputVideo);
|
||||||
if (scaling?.Size == null)
|
if (scaling?.Size == null)
|
||||||
|
@ -205,12 +188,6 @@ public void Convert(ContainerFormat type, Action<IMediaAnalysis> validationMetho
|
||||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Convert(ContainerFormat type, params IArgument[] inputArguments)
|
public void Convert(ContainerFormat type, params IArgument[] inputArguments)
|
||||||
{
|
{
|
||||||
|
@ -219,12 +196,10 @@ public void Convert(ContainerFormat type, params IArgument[] inputArguments)
|
||||||
|
|
||||||
public void ConvertFromPipe(ContainerFormat type, System.Drawing.Imaging.PixelFormat fmt, params IArgument[] arguments)
|
public void ConvertFromPipe(ContainerFormat type, System.Drawing.Imaging.PixelFormat fmt, params IArgument[] arguments)
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(type);
|
using var outputFile = new TemporaryFile($"out{type.Extension}");
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, fmt, 256, 256));
|
var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, fmt, 256, 256));
|
||||||
var processor = FFMpegArguments.FromPipeInput(videoFramesSource).OutputToFile(output, false, opt =>
|
var processor = FFMpegArguments.FromPipeInput(videoFramesSource).OutputToFile(outputFile, false, opt =>
|
||||||
{
|
{
|
||||||
foreach (var arg in arguments)
|
foreach (var arg in arguments)
|
||||||
opt.WithArgument(arg);
|
opt.WithArgument(arg);
|
||||||
|
@ -232,9 +207,9 @@ public void ConvertFromPipe(ContainerFormat type, System.Drawing.Imaging.PixelFo
|
||||||
var scaling = arguments.OfType<ScaleArgument>().FirstOrDefault();
|
var scaling = arguments.OfType<ScaleArgument>().FirstOrDefault();
|
||||||
processor.ProcessSynchronously();
|
processor.ProcessSynchronously();
|
||||||
|
|
||||||
var outputVideo = FFProbe.Analyse(output);
|
var outputVideo = FFProbe.Analyse(outputFile);
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
|
|
||||||
if (scaling?.Size == null)
|
if (scaling?.Size == null)
|
||||||
{
|
{
|
||||||
|
@ -257,13 +232,6 @@ public void ConvertFromPipe(ContainerFormat type, System.Drawing.Imaging.PixelFo
|
||||||
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_ToMP4()
|
public void Video_ToMP4()
|
||||||
|
@ -287,7 +255,6 @@ public void Video_ToMP4_Args()
|
||||||
[DataTestMethod, Timeout(10000)]
|
[DataTestMethod, Timeout(10000)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
|
||||||
public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||||
{
|
{
|
||||||
ConvertFromPipe(VideoType.Mp4, pixelFormat, new VideoCodecArgument(VideoCodec.LibX264));
|
ConvertFromPipe(VideoType.Mp4, pixelFormat, new VideoCodecArgument(VideoCodec.LibX264));
|
||||||
|
@ -307,11 +274,26 @@ await Assert.ThrowsExceptionAsync<FFMpegException>(async () =>
|
||||||
await using var ms = new MemoryStream();
|
await using var ms = new MemoryStream();
|
||||||
var pipeSource = new StreamPipeSink(ms);
|
var pipeSource = new StreamPipeSink(ms);
|
||||||
await FFMpegArguments
|
await FFMpegArguments
|
||||||
.FromFileInput(VideoLibrary.LocalVideo)
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToPipe(pipeSource, opt => opt.ForceFormat("mkv"))
|
.OutputToPipe(pipeSource, opt => opt.ForceFormat("mkv"))
|
||||||
.ProcessAsynchronously();
|
.ProcessAsynchronously();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
[TestMethod, Timeout(10000)]
|
||||||
|
public void Video_StreamFile_OutputToMemoryStream()
|
||||||
|
{
|
||||||
|
var output = new MemoryStream();
|
||||||
|
|
||||||
|
FFMpegArguments
|
||||||
|
.FromPipeInput(new StreamPipeSource(File.OpenRead(TestResources.WebmVideo)), options => options.ForceFormat("webm"))
|
||||||
|
.OutputToPipe(new StreamPipeSink(output), options => options
|
||||||
|
.ForceFormat("mpegts"))
|
||||||
|
.ProcessSynchronously();
|
||||||
|
|
||||||
|
output.Position = 0;
|
||||||
|
var result = FFProbe.Analyse(output);
|
||||||
|
Console.WriteLine(result.Duration);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_ToMP4_Args_StreamOutputPipe_Failure()
|
public void Video_ToMP4_Args_StreamOutputPipe_Failure()
|
||||||
|
@ -326,7 +308,7 @@ public void Video_ToMP4_Args_StreamOutputPipe_Async()
|
||||||
using var ms = new MemoryStream();
|
using var ms = new MemoryStream();
|
||||||
var pipeSource = new StreamPipeSink(ms);
|
var pipeSource = new StreamPipeSink(ms);
|
||||||
FFMpegArguments
|
FFMpegArguments
|
||||||
.FromFileInput(VideoLibrary.LocalVideo)
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToPipe(pipeSource, opt => opt
|
.OutputToPipe(pipeSource, opt => opt
|
||||||
.WithVideoCodec(VideoCodec.LibX264)
|
.WithVideoCodec(VideoCodec.LibX264)
|
||||||
.ForceFormat("matroska"))
|
.ForceFormat("matroska"))
|
||||||
|
@ -337,11 +319,11 @@ public void Video_ToMP4_Args_StreamOutputPipe_Async()
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public async Task TestDuplicateRun()
|
public async Task TestDuplicateRun()
|
||||||
{
|
{
|
||||||
FFMpegArguments.FromFileInput(VideoLibrary.LocalVideo)
|
FFMpegArguments.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToFile("temporary.mp4")
|
.OutputToFile("temporary.mp4")
|
||||||
.ProcessSynchronously();
|
.ProcessSynchronously();
|
||||||
|
|
||||||
await FFMpegArguments.FromFileInput(VideoLibrary.LocalVideo)
|
await FFMpegArguments.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToFile("temporary.mp4")
|
.OutputToFile("temporary.mp4")
|
||||||
.ProcessAsynchronously();
|
.ProcessAsynchronously();
|
||||||
|
|
||||||
|
@ -372,7 +354,6 @@ public void Video_ToTS_Args()
|
||||||
[DataTestMethod, Timeout(10000)]
|
[DataTestMethod, Timeout(10000)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
|
||||||
public void Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
public void Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||||
{
|
{
|
||||||
ConvertFromPipe(VideoType.Ts, pixelFormat, new ForceFormatArgument(VideoType.Ts));
|
ConvertFromPipe(VideoType.Ts, pixelFormat, new ForceFormatArgument(VideoType.Ts));
|
||||||
|
@ -447,63 +428,42 @@ public void Video_ToOGV_MultiThread()
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_Snapshot_InMemory()
|
public void Video_Snapshot_InMemory()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(ImageType.Png);
|
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var input = FFProbe.Analyse(Input.FullName);
|
|
||||||
|
|
||||||
using var bitmap = FFMpeg.Snapshot(input);
|
using var bitmap = FFMpeg.Snapshot(input);
|
||||||
|
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width);
|
Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width);
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
||||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_Snapshot_PersistSnapshot()
|
public void Video_Snapshot_PersistSnapshot()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(ImageType.Png);
|
var outputPath = new TemporaryFile("out.png");
|
||||||
try
|
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
{
|
|
||||||
var input = FFProbe.Analyse(Input.FullName);
|
|
||||||
|
|
||||||
FFMpeg.Snapshot(input, output);
|
FFMpeg.Snapshot(input, outputPath);
|
||||||
|
|
||||||
var bitmap = Image.FromFile(output);
|
using var bitmap = Image.FromFile(outputPath);
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width);
|
Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width);
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
||||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||||
bitmap.Dispose();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_Join()
|
public void Video_Join()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
var inputCopy = new TemporaryFile("copy-input.mp4");
|
||||||
var newInput = Input.OutputLocation(VideoType.Mp4.Name, "duplicate");
|
File.Copy(TestResources.Mp4Video, inputCopy);
|
||||||
try
|
|
||||||
{
|
|
||||||
var input = FFProbe.Analyse(Input.FullName);
|
|
||||||
File.Copy(Input.FullName, newInput);
|
|
||||||
|
|
||||||
var success = FFMpeg.Join(output, Input.FullName, newInput);
|
var outputPath = new TemporaryFile("out.mp4");
|
||||||
|
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
|
var success = FFMpeg.Join(outputPath, TestResources.Mp4Video, inputCopy);
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
|
Assert.IsTrue(File.Exists(outputPath));
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output));
|
|
||||||
var expectedDuration = input.Duration * 2;
|
var expectedDuration = input.Duration * 2;
|
||||||
var result = FFProbe.Analyse(output);
|
var result = FFProbe.Analyse(outputPath);
|
||||||
Assert.AreEqual(expectedDuration.Days, result.Duration.Days);
|
Assert.AreEqual(expectedDuration.Days, result.Duration.Days);
|
||||||
Assert.AreEqual(expectedDuration.Hours, result.Duration.Hours);
|
Assert.AreEqual(expectedDuration.Hours, result.Duration.Hours);
|
||||||
Assert.AreEqual(expectedDuration.Minutes, result.Duration.Minutes);
|
Assert.AreEqual(expectedDuration.Minutes, result.Duration.Minutes);
|
||||||
|
@ -511,24 +471,12 @@ public void Video_Join()
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Height, result.PrimaryVideoStream.Height);
|
Assert.AreEqual(input.PrimaryVideoStream.Height, result.PrimaryVideoStream.Height);
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
|
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
|
|
||||||
if (File.Exists(newInput))
|
|
||||||
File.Delete(newInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_Join_Image_Sequence()
|
public void Video_Join_Image_Sequence()
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var imageSet = new List<ImageInfo>();
|
var imageSet = new List<ImageInfo>();
|
||||||
Directory.EnumerateFiles(VideoLibrary.ImageDirectory.FullName)
|
Directory.EnumerateFiles(TestResources.ImageCollection)
|
||||||
.Where(file => file.ToLower().EndsWith(".png"))
|
.Where(file => file.ToLower().EndsWith(".png"))
|
||||||
.ToList()
|
.ToList()
|
||||||
.ForEach(file =>
|
.ForEach(file =>
|
||||||
|
@ -539,99 +487,68 @@ public void Video_Join_Image_Sequence()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var success = FFMpeg.JoinImageSequence(VideoLibrary.ImageJoinOutput.FullName, images: imageSet.ToArray());
|
var outputFile = new TemporaryFile("out.mp4");
|
||||||
|
var success = FFMpeg.JoinImageSequence(outputFile, images: imageSet.ToArray());
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
var result = FFProbe.Analyse(VideoLibrary.ImageJoinOutput.FullName);
|
var result = FFProbe.Analyse(outputFile);
|
||||||
|
|
||||||
VideoLibrary.ImageJoinOutput.Refresh();
|
|
||||||
|
|
||||||
Assert.IsTrue(VideoLibrary.ImageJoinOutput.Exists);
|
|
||||||
Assert.AreEqual(3, result.Duration.Seconds);
|
Assert.AreEqual(3, result.Duration.Seconds);
|
||||||
Assert.AreEqual(imageSet.First().Width, result.PrimaryVideoStream.Width);
|
Assert.AreEqual(imageSet.First().Width, result.PrimaryVideoStream.Width);
|
||||||
Assert.AreEqual(imageSet.First().Height, result.PrimaryVideoStream.Height);
|
Assert.AreEqual(imageSet.First().Height, result.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
VideoLibrary.ImageJoinOutput.Refresh();
|
|
||||||
if (VideoLibrary.ImageJoinOutput.Exists)
|
|
||||||
{
|
|
||||||
VideoLibrary.ImageJoinOutput.Delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_With_Only_Audio_Should_Extract_Metadata()
|
public void Video_With_Only_Audio_Should_Extract_Metadata()
|
||||||
{
|
{
|
||||||
var video = FFProbe.Analyse(VideoLibrary.LocalVideoAudioOnly.FullName);
|
var video = FFProbe.Analyse(TestResources.Mp4WithoutVideo);
|
||||||
Assert.AreEqual(null, video.PrimaryVideoStream);
|
Assert.AreEqual(null, video.PrimaryVideoStream);
|
||||||
Assert.AreEqual("aac", video.PrimaryAudioStream.CodecName);
|
Assert.AreEqual("aac", video.PrimaryAudioStream.CodecName);
|
||||||
Assert.AreEqual(10, video.Duration.TotalSeconds, 0.5);
|
Assert.AreEqual(10, video.Duration.TotalSeconds, 0.5);
|
||||||
// Assert.AreEqual(1.25, video.Size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_Duration()
|
public void Video_Duration()
|
||||||
{
|
{
|
||||||
var video = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
var video = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
var outputFile = new TemporaryFile("out.mp4");
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
FFMpegArguments
|
FFMpegArguments
|
||||||
.FromFileInput(VideoLibrary.LocalVideo)
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToFile(output, false, opt => opt.WithDuration(TimeSpan.FromSeconds(video.Duration.TotalSeconds - 2)))
|
.OutputToFile(outputFile, false, opt => opt.WithDuration(TimeSpan.FromSeconds(video.Duration.TotalSeconds - 2)))
|
||||||
.ProcessSynchronously();
|
.ProcessSynchronously();
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
var outputVideo = FFProbe.Analyse(output);
|
var outputVideo = FFProbe.Analyse(outputFile);
|
||||||
|
|
||||||
Assert.AreEqual(video.Duration.Days, outputVideo.Duration.Days);
|
Assert.AreEqual(video.Duration.Days, outputVideo.Duration.Days);
|
||||||
Assert.AreEqual(video.Duration.Hours, outputVideo.Duration.Hours);
|
Assert.AreEqual(video.Duration.Hours, outputVideo.Duration.Hours);
|
||||||
Assert.AreEqual(video.Duration.Minutes, outputVideo.Duration.Minutes);
|
Assert.AreEqual(video.Duration.Minutes, outputVideo.Duration.Minutes);
|
||||||
Assert.AreEqual(video.Duration.Seconds - 2, outputVideo.Duration.Seconds);
|
Assert.AreEqual(video.Duration.Seconds - 2, outputVideo.Duration.Seconds);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_UpdatesProgress()
|
public void Video_UpdatesProgress()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
var outputFile = new TemporaryFile("out.mp4");
|
||||||
|
|
||||||
var percentageDone = 0.0;
|
var percentageDone = 0.0;
|
||||||
var timeDone = TimeSpan.Zero;
|
var timeDone = TimeSpan.Zero;
|
||||||
void OnPercentageProgess(double percentage) => percentageDone = percentage;
|
void OnPercentageProgess(double percentage) => percentageDone = percentage;
|
||||||
void OnTimeProgess(TimeSpan time) => timeDone = time;
|
void OnTimeProgess(TimeSpan time) => timeDone = time;
|
||||||
|
|
||||||
var analysis = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
var analysis = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var success = FFMpegArguments
|
var success = FFMpegArguments
|
||||||
.FromFileInput(VideoLibrary.LocalVideo)
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToFile(output, false, opt => opt
|
.OutputToFile(outputFile, false, opt => opt
|
||||||
.WithDuration(TimeSpan.FromSeconds(2)))
|
.WithDuration(TimeSpan.FromSeconds(2)))
|
||||||
.NotifyOnProgress(OnPercentageProgess, analysis.Duration)
|
.NotifyOnProgress(OnPercentageProgess, analysis.Duration)
|
||||||
.NotifyOnProgress(OnTimeProgess)
|
.NotifyOnProgress(OnTimeProgess)
|
||||||
.ProcessSynchronously();
|
.ProcessSynchronously();
|
||||||
|
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
Assert.IsTrue(File.Exists(output));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
Assert.AreNotEqual(0.0, percentageDone);
|
Assert.AreNotEqual(0.0, percentageDone);
|
||||||
Assert.AreNotEqual(TimeSpan.Zero, timeDone);
|
Assert.AreNotEqual(TimeSpan.Zero, timeDone);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_TranscodeInMemory()
|
public void Video_TranscodeInMemory()
|
||||||
|
@ -656,11 +573,11 @@ public void Video_TranscodeInMemory()
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public async Task Video_Cancel_Async()
|
public async Task Video_Cancel_Async()
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
var outputFile = new TemporaryFile("out.mp4");
|
||||||
|
|
||||||
var task = FFMpegArguments
|
var task = FFMpegArguments
|
||||||
.FromFileInput(VideoLibrary.LocalVideo)
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToFile(output, false, opt => opt
|
.OutputToFile(outputFile, false, opt => opt
|
||||||
.Resize(new Size(1000, 1000))
|
.Resize(new Size(1000, 1000))
|
||||||
.WithAudioCodec(AudioCodec.Aac)
|
.WithAudioCodec(AudioCodec.Aac)
|
||||||
.WithVideoCodec(VideoCodec.LibX264)
|
.WithVideoCodec(VideoCodec.LibX264)
|
||||||
|
@ -670,19 +587,11 @@ public async Task Video_Cancel_Async()
|
||||||
.CancellableThrough(out var cancel)
|
.CancellableThrough(out var cancel)
|
||||||
.ProcessAsynchronously(false);
|
.ProcessAsynchronously(false);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(300);
|
await Task.Delay(300);
|
||||||
cancel();
|
cancel();
|
||||||
|
|
||||||
var result = await task;
|
var result = await task;
|
||||||
Assert.IsFalse(result);
|
Assert.IsFalse(result);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(output))
|
|
||||||
File.Delete(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,14 @@ public InputPipeArgument(IPipeSource writer) : base(PipeDirection.Out)
|
||||||
Writer = writer;
|
Writer = writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Text => $"-y {Writer.GetFormat()} -i \"{PipePath}\"";
|
public override string Text => $"-y {Writer.GetStreamArguments()} -i \"{PipePath}\"";
|
||||||
|
|
||||||
protected override async Task ProcessDataAsync(CancellationToken token)
|
protected override async Task ProcessDataAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||||
if (!Pipe.IsConnected)
|
if (!Pipe.IsConnected)
|
||||||
throw new TaskCanceledException();
|
throw new TaskCanceledException();
|
||||||
await Writer.CopyAsync(Pipe, token).ConfigureAwait(false);
|
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ protected override async Task ProcessDataAsync(CancellationToken token)
|
||||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||||
if (!Pipe.IsConnected)
|
if (!Pipe.IsConnected)
|
||||||
throw new TaskCanceledException();
|
throw new TaskCanceledException();
|
||||||
await Reader.CopyAsync(Pipe, token).ConfigureAwait(false);
|
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -30,6 +31,7 @@ public void Pre()
|
||||||
|
|
||||||
public void Post()
|
public void Post()
|
||||||
{
|
{
|
||||||
|
Debug.WriteLine($"Disposing NamedPipeServerStream on {GetType().Name}");
|
||||||
Pipe?.Dispose();
|
Pipe?.Dispose();
|
||||||
Pipe = null!;
|
Pipe = null!;
|
||||||
}
|
}
|
||||||
|
@ -39,11 +41,13 @@ public async Task During(CancellationToken cancellationToken = default)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ProcessDataAsync(cancellationToken);
|
await ProcessDataAsync(cancellationToken);
|
||||||
|
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
|
||||||
|
Pipe?.Disconnect();
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
|
Debug.WriteLine($"ProcessDataAsync on {GetType().Name} cancelled");
|
||||||
}
|
}
|
||||||
Pipe.Disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Task ProcessDataAsync(CancellationToken token);
|
protected abstract Task ProcessDataAsync(CancellationToken token);
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
public class ContainerFormat
|
public class ContainerFormat
|
||||||
{
|
{
|
||||||
private static readonly Regex _formatRegex = new Regex(@"([D ])([E ])\s+([a-z0-9_]+)\s+(.+)");
|
private static readonly Regex FormatRegex = new Regex(@"([D ])([E ])\s+([a-z0-9_]+)\s+(.+)");
|
||||||
|
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public bool DemuxingSupported { get; private set; }
|
public bool DemuxingSupported { get; private set; }
|
||||||
|
@ -27,17 +27,19 @@ internal ContainerFormat(string name)
|
||||||
|
|
||||||
internal static bool TryParse(string line, out ContainerFormat fmt)
|
internal static bool TryParse(string line, out ContainerFormat fmt)
|
||||||
{
|
{
|
||||||
var match = _formatRegex.Match(line);
|
var match = FormatRegex.Match(line);
|
||||||
if (!match.Success)
|
if (!match.Success)
|
||||||
{
|
{
|
||||||
fmt = null!;
|
fmt = null!;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt = new ContainerFormat(match.Groups[3].Value);
|
fmt = new ContainerFormat(match.Groups[3].Value)
|
||||||
fmt.DemuxingSupported = match.Groups[1].Value == " ";
|
{
|
||||||
fmt.MuxingSupported = match.Groups[2].Value == " ";
|
DemuxingSupported = match.Groups[1].Value == " ",
|
||||||
fmt.Description = match.Groups[4].Value;
|
MuxingSupported = match.Groups[2].Value == " ",
|
||||||
|
Description = match.Groups[4].Value
|
||||||
|
};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,16 @@ public enum FFMpegExceptionType
|
||||||
|
|
||||||
public class FFMpegException : Exception
|
public class FFMpegException : Exception
|
||||||
{
|
{
|
||||||
|
public FFMpegException(FFMpegExceptionType type, string? message = null, Exception? innerException = null, string ffmpegErrorOutput = "", string ffmpegOutput = "")
|
||||||
public FFMpegException(FFMpegExceptionType type, string? message = null, Exception? innerException = null, string ffMpegErrorOutput = "")
|
|
||||||
: base(message, innerException)
|
: base(message, innerException)
|
||||||
{
|
{
|
||||||
FFMpegErrorOutput = ffMpegErrorOutput;
|
FfmpegOutput = ffmpegOutput;
|
||||||
|
FfmpegErrorOutput = ffmpegErrorOutput;
|
||||||
Type = type;
|
Type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FFMpegExceptionType Type { get; }
|
public FFMpegExceptionType Type { get; }
|
||||||
public string FFMpegErrorOutput { get; }
|
public string FfmpegOutput { get; }
|
||||||
|
public string FfmpegErrorOutput { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -72,7 +72,8 @@ public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan
|
||||||
.ProcessSynchronously();
|
.ProcessSynchronously();
|
||||||
|
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
return new Bitmap(ms);
|
using var bitmap = new Bitmap(ms);
|
||||||
|
return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||||
|
|
|
@ -70,20 +70,20 @@ void OnCancelEvent(object sender, EventArgs args)
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (!HandleException(throwOnError, e, instance.ErrorData)) return false;
|
if (!HandleException(throwOnError, e, instance.ErrorData, instance.OutputData)) return false;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
CancelEvent -= OnCancelEvent;
|
CancelEvent -= OnCancelEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleCompletion(throwOnError, errorCode, instance.ErrorData);
|
return HandleCompletion(throwOnError, errorCode, instance.ErrorData, instance.OutputData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HandleCompletion(bool throwOnError, int errorCode, IReadOnlyList<string> errorData)
|
private bool HandleCompletion(bool throwOnError, int errorCode, IReadOnlyList<string> errorData, IReadOnlyList<string> outputData)
|
||||||
{
|
{
|
||||||
if (throwOnError && errorCode != 0)
|
if (throwOnError && errorCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Conversion, string.Join("\n", errorData));
|
throw new FFMpegException(FFMpegExceptionType.Conversion, "FFMpeg exited with non-zero exitcode.", null, string.Join("\n", errorData), string.Join("\n", outputData));
|
||||||
|
|
||||||
_onPercentageProgress?.Invoke(100.0);
|
_onPercentageProgress?.Invoke(100.0);
|
||||||
if (_totalTimespan.HasValue) _onTimeProgress?.Invoke(_totalTimespan.Value);
|
if (_totalTimespan.HasValue) _onTimeProgress?.Invoke(_totalTimespan.Value);
|
||||||
|
@ -98,7 +98,7 @@ public async Task<bool> ProcessAsynchronously(bool throwOnError = true)
|
||||||
|
|
||||||
void OnCancelEvent(object sender, EventArgs args)
|
void OnCancelEvent(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
instance?.SendInput("q");
|
instance.SendInput("q");
|
||||||
cancellationTokenSource.Cancel();
|
cancellationTokenSource.Cancel();
|
||||||
instance.Started = false;
|
instance.Started = false;
|
||||||
}
|
}
|
||||||
|
@ -116,14 +116,14 @@ await Task.WhenAll(instance.FinishedRunning().ContinueWith(t =>
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (!HandleException(throwOnError, e, instance.ErrorData)) return false;
|
if (!HandleException(throwOnError, e, instance.ErrorData, instance.OutputData)) return false;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
CancelEvent -= OnCancelEvent;
|
CancelEvent -= OnCancelEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleCompletion(throwOnError, errorCode, instance.ErrorData);
|
return HandleCompletion(throwOnError, errorCode, instance.ErrorData, instance.OutputData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSource)
|
private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSource)
|
||||||
|
@ -140,13 +140,12 @@ private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static bool HandleException(bool throwOnError, Exception e, IReadOnlyList<string> errorData)
|
private static bool HandleException(bool throwOnError, Exception e, IReadOnlyList<string> errorData, IReadOnlyList<string> outputData)
|
||||||
{
|
{
|
||||||
if (!throwOnError)
|
if (!throwOnError)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e,
|
throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e, string.Join("\n", errorData), string.Join("\n", outputData));
|
||||||
string.Join("\n", errorData));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OutputData(object sender, (DataType Type, string Data) msg)
|
private void OutputData(object sender, (DataType Type, string Data) msg)
|
||||||
|
|
|
@ -32,7 +32,7 @@ static FFMpegOptions()
|
||||||
{
|
{
|
||||||
if (File.Exists(ConfigFile))
|
if (File.Exists(ConfigFile))
|
||||||
{
|
{
|
||||||
Options = JsonSerializer.Deserialize<FFMpegOptions>(File.ReadAllText(ConfigFile));
|
Options = JsonSerializer.Deserialize<FFMpegOptions>(File.ReadAllText(ConfigFile))!;
|
||||||
foreach (var pair in DefaultExtensionsOverrides)
|
foreach (var pair in DefaultExtensionsOverrides)
|
||||||
if (!Options.ExtensionOverrides.ContainsKey(pair.Key)) Options.ExtensionOverrides.Add(pair.Key, pair.Value);
|
if (!Options.ExtensionOverrides.ContainsKey(pair.Key)) Options.ExtensionOverrides.Add(pair.Key, pair.Value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace FFMpegCore.Pipes
|
||||||
{
|
{
|
||||||
public interface IPipeSink
|
public interface IPipeSink
|
||||||
{
|
{
|
||||||
Task CopyAsync(System.IO.Stream inputStream, CancellationToken cancellationToken);
|
Task ReadAsync(System.IO.Stream inputStream, CancellationToken cancellationToken);
|
||||||
string GetFormat();
|
string GetFormat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace FFMpegCore.Pipes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPipeSource
|
public interface IPipeSource
|
||||||
{
|
{
|
||||||
string GetFormat();
|
string GetStreamArguments();
|
||||||
Task CopyAsync(System.IO.Stream outputStream, CancellationToken cancellationToken);
|
Task WriteAsync(System.IO.Stream outputStream, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ public RawVideoPipeSource(IEnumerator<IVideoFrame> framesEnumerator)
|
||||||
|
|
||||||
public RawVideoPipeSource(IEnumerable<IVideoFrame> framesEnumerator) : this(framesEnumerator.GetEnumerator()) { }
|
public RawVideoPipeSource(IEnumerable<IVideoFrame> framesEnumerator) : this(framesEnumerator.GetEnumerator()) { }
|
||||||
|
|
||||||
public string GetFormat()
|
public string GetStreamArguments()
|
||||||
{
|
{
|
||||||
if (!_formatInitialized)
|
if (!_formatInitialized)
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,7 @@ public string GetFormat()
|
||||||
return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}";
|
return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CopyAsync(System.IO.Stream outputStream, CancellationToken cancellationToken)
|
public async Task WriteAsync(System.IO.Stream outputStream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_framesEnumerator.Current != null)
|
if (_framesEnumerator.Current != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
using System.Threading;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FFMpegCore.Pipes
|
namespace FFMpegCore.Pipes
|
||||||
{
|
{
|
||||||
public class StreamPipeSink : IPipeSink
|
public class StreamPipeSink : IPipeSink
|
||||||
{
|
{
|
||||||
public System.IO.Stream Destination { get; }
|
public Func<Stream, CancellationToken, Task> Writer { get; }
|
||||||
public int BlockSize { get; set; } = 4096;
|
public int BlockSize { get; set; } = 4096;
|
||||||
public string Format { get; set; } = string.Empty;
|
public string Format { get; set; } = string.Empty;
|
||||||
|
|
||||||
public StreamPipeSink(System.IO.Stream destination)
|
public StreamPipeSink(Func<Stream, CancellationToken, Task> writer)
|
||||||
{
|
{
|
||||||
Destination = destination;
|
Writer = writer;
|
||||||
|
}
|
||||||
|
public StreamPipeSink(Stream destination)
|
||||||
|
{
|
||||||
|
Writer = (inputStream, cancellationToken) => inputStream.CopyToAsync(destination, BlockSize, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CopyAsync(System.IO.Stream inputStream, CancellationToken cancellationToken) =>
|
public Task ReadAsync(System.IO.Stream inputStream, CancellationToken cancellationToken)
|
||||||
inputStream.CopyToAsync(Destination, BlockSize, cancellationToken);
|
=> Writer(inputStream, cancellationToken);
|
||||||
|
|
||||||
public string GetFormat() => Format;
|
public string GetFormat() => Format;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ public StreamPipeSource(System.IO.Stream source)
|
||||||
Source = source;
|
Source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CopyAsync(System.IO.Stream outputStream, CancellationToken cancellationToken) => Source.CopyToAsync(outputStream, BlockSize, cancellationToken);
|
public string GetStreamArguments() => StreamFormat;
|
||||||
|
|
||||||
public string GetFormat() => StreamFormat;
|
public Task WriteAsync(System.IO.Stream outputStream, CancellationToken cancellationToken) => Source.CopyToAsync(outputStream, BlockSize, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@
|
||||||
<Version>3.0.0.0</Version>
|
<Version>3.0.0.0</Version>
|
||||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
||||||
<FileVersion>3.0.0.0</FileVersion>
|
<FileVersion>3.0.0.0</FileVersion>
|
||||||
<PackageReleaseNotes>- Fix hanging pipes on unix sockets
|
<PackageReleaseNotes>- Also include ffmpeg output data on non-zero exit code</PackageReleaseNotes>
|
||||||
- Internal API cleanup</PackageReleaseNotes>
|
|
||||||
<LangVersion>8</LangVersion>
|
<LangVersion>8</LangVersion>
|
||||||
<PackageVersion>3.1.0</PackageVersion>
|
<PackageVersion>3.2.4</PackageVersion>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<Authors>Malte Rosenbjerg, Vlad Jerca</Authors>
|
<Authors>Malte Rosenbjerg, Vlad Jerca</Authors>
|
||||||
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags>
|
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags>
|
||||||
<RepositoryType>GitHub</RepositoryType>
|
<RepositoryType>GitHub</RepositoryType>
|
||||||
|
@ -29,8 +29,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Instances" Version="1.6.0" />
|
<PackageReference Include="Instances" Version="1.6.0" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
|
<PackageReference Include="System.Drawing.Common" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="4.7.2" />
|
<PackageReference Include="System.Text.Json" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
2
FFMpegCore/FFMpegCore.csproj.DotSettings
Normal file
2
FFMpegCore/FFMpegCore.csproj.DotSettings
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=ffmpeg/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
|
@ -46,7 +46,7 @@ public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.Max
|
||||||
}
|
}
|
||||||
var exitCode = task.ConfigureAwait(false).GetAwaiter().GetResult();
|
var exitCode = task.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}: {string.Join("\n", instance.OutputData)} {string.Join("\n", instance.ErrorData)}");
|
throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}", null, string.Join("\n", instance.ErrorData), string.Join("\n", instance.OutputData));
|
||||||
|
|
||||||
return ParseOutput(pipeArgument.PipePath, instance);
|
return ParseOutput(pipeArgument.PipePath, instance);
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, int outputC
|
||||||
}
|
}
|
||||||
var exitCode = await task;
|
var exitCode = await task;
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}: {string.Join("\n", instance.OutputData)} {string.Join("\n", instance.ErrorData)}");
|
throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}", null, string.Join("\n", instance.ErrorData), string.Join("\n", instance.OutputData));
|
||||||
|
|
||||||
pipeArgument.Post();
|
pipeArgument.Post();
|
||||||
return ParseOutput(pipeArgument.PipePath, instance);
|
return ParseOutput(pipeArgument.PipePath, instance);
|
||||||
|
@ -98,7 +98,7 @@ private static IMediaAnalysis ParseOutput(string filePath, Instance instance)
|
||||||
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeAnalysis>(json, new JsonSerializerOptions
|
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeAnalysis>(json, new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true
|
PropertyNameCaseInsensitive = true
|
||||||
});
|
})!;
|
||||||
return new MediaAnalysis(filePath, ffprobeAnalysis);
|
return new MediaAnalysis(filePath, ffprobeAnalysis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ private static Instance PrepareInstance(string filePath, int outputCapacity)
|
||||||
{
|
{
|
||||||
FFProbeHelper.RootExceptionCheck();
|
FFProbeHelper.RootExceptionCheck();
|
||||||
FFProbeHelper.VerifyFFProbeExists();
|
FFProbeHelper.VerifyFFProbeExists();
|
||||||
var arguments = $"-print_format json -show_format -sexagesimal -show_streams \"{filePath}\"";
|
var arguments = $"-loglevel error -print_format json -show_format -sexagesimal -show_streams \"{filePath}\"";
|
||||||
var instance = new Instance(FFMpegOptions.Options.FFProbeBinary(), arguments) {DataBufferCapacity = outputCapacity};
|
var instance = new Instance(FFMpegOptions.Options.FFProbeBinary(), arguments) {DataBufferCapacity = outputCapacity};
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue