├── MediaToolkit.Test
├── TestVideo
│ └── BigBunny.m4v
├── Tasks
│ ├── FrameSizeTest.cs
│ ├── FfTaskGetThumbnailTest.cs
│ └── FfTaskGetMetadataTest.cs
├── MediaToolkit.Test.csproj
├── EngineTest.cs
├── Util
│ ├── ExtensionTest.cs
│ └── RegexEngineTest.cs
├── Core
│ └── FfProcessFactoryTest.cs
├── Services
│ └── FfServiceTest.cs
└── ConvertTest.cs
├── MediaToolkit
├── Directory.Build.props
├── AssemblyInfo.cs
├── Options
│ ├── CropRectangle.cs
│ ├── ConversionEnums.cs
│ └── ConversionOptions.cs
├── Model
│ ├── FfProbeOutput.cs
│ ├── MediaFile.cs
│ ├── Disposition.cs
│ ├── Metadata.cs
│ ├── Format.cs
│ └── MediaStream.cs
├── Tasks
│ ├── PixelFormat.cs
│ ├── OutputFormat.cs
│ ├── FfMpegTaskBase.cs
│ ├── FfProbeTaskBase.cs
│ ├── GetThumbnailResult.cs
│ ├── GetMetadataResult.cs
│ ├── FrameSize.cs
│ ├── GetThumbnailOptions.cs
│ ├── FfTaskBase.cs
│ ├── FfTaskGetMetadata.cs
│ ├── FfTaskSaveThumbnail.cs
│ └── FfTaskGetThumbnail.cs
├── Services
│ ├── IMediaToolkitService.cs
│ ├── MediaToolkitOptions.cs
│ └── MediaToolkitService.cs
├── Core
│ ├── IffProcessFactory.cs
│ ├── FfTaskResult.cs
│ ├── IProcessStreamReader.cs
│ ├── IFfProcess.cs
│ ├── StreamReaderWrapper.cs
│ ├── FfProcessFactory.cs
│ └── FfProcess.cs
├── FFmpegTask.cs
├── MediaToolkit.csproj
├── MediaToolkit.NetCore.props
├── ServiceCollectionExtensions.cs
├── Util
│ ├── Document.cs
│ ├── Extensions.cs
│ └── RegexEngine.cs
├── ConvertProgressEventArgs.cs
├── ConversionCompleteEventArgs.cs
├── EngineParameters.cs
├── EngineBase.cs
├── Properties
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── CommandBuilder.cs
└── Engine.cs
├── .editorconfig
├── .vscode
├── settings.json
├── launch.json
└── tasks.json
├── NuGet.Config
├── NuGet.config
├── .gitattributes
├── SampleApp
├── SampleApp.csproj
└── Program.cs
├── LICENSE.md
├── MediaToolkit.sln
├── .gitignore
└── README.md
/MediaToolkit.Test/TestVideo/BigBunny.m4v:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mtebenev/MediaToolkit.NetCore/HEAD/MediaToolkit.Test/TestVideo/BigBunny.m4v
--------------------------------------------------------------------------------
/MediaToolkit/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/MediaToolkit/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | [assembly: InternalsVisibleTo("MediaToolkit.Test")]
3 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.watcherExclude": {
3 | "**/.git/": true,
4 | "**/bin/**": true,
5 | "**/obj/**": true
6 | },
7 | "files.exclude": {
8 | "**/bin/": true,
9 | "**/obj/": true
10 | },
11 | "editor.tabSize": 2
12 | }
--------------------------------------------------------------------------------
/MediaToolkit/Options/CropRectangle.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Options
2 | {
3 | public class CropRectangle
4 | {
5 | public int X { get; set; }
6 |
7 | public int Y { get; set; }
8 |
9 | public int Width { get; set; }
10 |
11 | public int Height { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/MediaToolkit/Model/FfProbeOutput.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace MediaToolkit.Model
4 | {
5 | ///
6 | /// DTO for ffprobe output.
7 | ///
8 | public class FfProbeOutput
9 | {
10 | public IList Streams { get; set; }
11 | public Format Format { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/PixelFormat.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Tasks
2 | {
3 | ///
4 | /// Some well-known pixel formats.
5 | ///
6 | public static class PixelFormat
7 | {
8 | public static string Argb = "argb";
9 | public static string Gray = "gray";
10 | public static string Rgba = "rgba";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MediaToolkit/Model/MediaFile.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Model
2 | {
3 | public class MediaFile
4 | {
5 | public MediaFile() { }
6 |
7 | public MediaFile(string filename)
8 | {
9 | Filename = filename;
10 | }
11 |
12 | public string Filename { get; set; }
13 |
14 | public Metadata Metadata { get; internal set; }
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/OutputFormat.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Tasks
2 | {
3 | ///
4 | /// Some well-known formats for -f option of the ffmpeg.
5 | ///
6 | public static class OutputFormat
7 | {
8 | public static string Gif = "gif";
9 | public static string Image2 = "image2";
10 | public static string RawVideo = "rawvideo";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/Tasks/FrameSizeTest.cs:
--------------------------------------------------------------------------------
1 | using MediaToolkit.Tasks;
2 | using Xunit;
3 |
4 | namespace MediaToolkit.Test.Tasks
5 | {
6 | public class FrameSizeTest
7 | {
8 | [Fact]
9 | public void Should_Produce_Ffmpeg_Size()
10 | {
11 | var frameSize = new FrameSize(100, 100);
12 | Assert.Equal("100x100", frameSize.ToString());
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/MediaToolkit/Services/IMediaToolkitService.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using MediaToolkit.Tasks;
3 |
4 | namespace MediaToolkit.Services
5 | {
6 | ///
7 | /// The service for invoking commands for ffmpeg and ffprobe.
8 | ///
9 | public interface IMediaToolkitService
10 | {
11 | Task ExecuteAsync(FfTaskBase task);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/FfMpegTaskBase.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using MediaToolkit.Services;
3 |
4 | namespace MediaToolkit.Tasks
5 | {
6 | ///
7 | /// The base class for all ffmpeg tasks.
8 | ///
9 | public abstract class FfMpegTaskBase : FfTaskBase
10 | {
11 | internal override Task ExecuteAsync(MediaToolkitService ffService)
12 | {
13 | return ffService.ExecuteAsync(this);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/FfProbeTaskBase.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using MediaToolkit.Services;
3 |
4 | namespace MediaToolkit.Tasks
5 | {
6 | ///
7 | /// The base class for all ffmpeg tasks.
8 | ///
9 | public abstract class FfProbeTaskBase : FfTaskBase
10 | {
11 | internal override Task ExecuteAsync(MediaToolkitService ffService)
12 | {
13 | return ffService.ExecuteAsync(this);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/GetThumbnailResult.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Tasks
2 | {
3 | ///
4 | /// The result type for get thumbnail task.
5 | ///
6 | public class GetThumbnailResult
7 | {
8 | public GetThumbnailResult(byte[] thumbnailData)
9 | {
10 | this.ThumbnailData = thumbnailData;
11 | }
12 |
13 | ///
14 | /// The thumbnail data.
15 | ///
16 | public byte[] ThumbnailData { get ; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/GetMetadataResult.cs:
--------------------------------------------------------------------------------
1 | using MediaToolkit.Model;
2 |
3 | namespace MediaToolkit.Tasks
4 | {
5 | ///
6 | /// The result type for get metadata task.
7 | ///
8 | public class GetMetadataResult
9 | {
10 | public GetMetadataResult(FfProbeOutput metadata)
11 | {
12 | this.Metadata = metadata;
13 | }
14 |
15 | ///
16 | /// The result metadata.
17 | ///
18 | public FfProbeOutput Metadata { get; private set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/FrameSize.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Tasks
2 | {
3 | ///
4 | /// Describes the width/height of the video frame.
5 | ///
6 | public class FrameSize
7 | {
8 | public FrameSize(int width, int height)
9 | {
10 | this.Width = width;
11 | this.Height = height;
12 | }
13 |
14 | public int Width { get; }
15 | public int Height { get; }
16 |
17 | public override string ToString()
18 | {
19 | return $"{this.Width}x{this.Height}";
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/MediaToolkit/Core/IffProcessFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace MediaToolkit.Core
4 | {
5 | ///
6 | /// Factory service for FF process
7 | ///
8 | public interface IffProcessFactory
9 | {
10 | ///
11 | /// Launches the ffmpeg
12 | ///
13 | IFfProcess LaunchFfMpeg(IEnumerable arguments);
14 |
15 | ///
16 | /// Launches the ffprobe
17 | ///
18 | IFfProcess LaunchFfProbe(IEnumerable arguments);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/MediaToolkit/Core/FfTaskResult.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Core
2 | {
3 | ///
4 | /// The ffmpeg/ffprobe execution result
5 | ///
6 | public class FfTaskResult
7 | {
8 | public FfTaskResult(string output, string error)
9 | {
10 | this.Output = output;
11 | this.Error = error;
12 | }
13 |
14 | ///
15 | /// The standard output.
16 | ///
17 | public string Output { get; private set; }
18 |
19 | ///
20 | /// The error output.
21 | ///
22 | public string Error { get; private set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/MediaToolkit/FFmpegTask.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit
2 | {
3 | /// -------------------------------------------------------------------------------------------------
4 | /// Values that represent fmpeg tasks.
5 | internal enum FFmpegTask
6 | {
7 | /// An enum constant representing the convert option.
8 | Convert,
9 |
10 | /// An enum constant representing the get meta data option.
11 | GetMetaData,
12 |
13 | /// An enum constant representing the get thumbnail option.
14 | GetThumbnail
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MediaToolkit/Core/IProcessStreamReader.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Threading.Tasks;
3 |
4 | namespace MediaToolkit.Core
5 | {
6 | ///
7 | /// The process stream reader interface.
8 | ///
9 | public interface IProcessStreamReader
10 | {
11 | ///
12 | /// The underlying stream.
13 | ///
14 | Stream BaseStream { get; }
15 |
16 | ///
17 | /// Reads all characters from the current position to the end of the text reader
18 | /// asynchronously and returns them as one string.
19 | ///
20 | Task ReadToEndAsync();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/MediaToolkit/Services/MediaToolkitOptions.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Services
2 | {
3 | ///
4 | /// The MediaToolkit configuration.
5 | ///
6 | public class MediaToolkitOptions
7 | {
8 | ///
9 | /// The absolute path to the ffmpeg.exe
10 | ///
11 | public string FfMpegPath { get; set; }
12 |
13 | ///
14 | /// The absolute path to the ffprobe.
15 | /// Pass null to use ffprobe.exe in the same directory as the ffmpeg (works on Windows only)
16 | ///
17 | ///
18 | public string FfProbePath { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | false
7 |
8 |
9 |
10 | latest
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/MediaToolkit/Core/IFfProcess.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Medallion.Shell.Streams;
3 |
4 | namespace MediaToolkit.Core
5 | {
6 | ///
7 | /// The interface for executed FF tool process.
8 | ///
9 | public interface IFfProcess
10 | {
11 | ///
12 | /// The task awaiting the process complete.
13 | ///
14 | Task Task { get; }
15 |
16 | ///
17 | /// The standard output.
18 | ///
19 | IProcessStreamReader OutputReader { get; }
20 |
21 | ///
22 | /// The standard error.
23 | ///
24 | IProcessStreamReader ErrorReader { get; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/MediaToolkit/MediaToolkit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | MediaToolkit.NetCore
5 | MediaToolkit port to .Net Core
6 | netstandard2.0
7 | MediaToolkit
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/GetThumbnailOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MediaToolkit.Tasks
4 | {
5 | ///
6 | /// Input options for the get thumbnail task.
7 | ///
8 | public class GetThumbnailOptions
9 | {
10 | ///
11 | /// The video fram time span.
12 | ///
13 | public TimeSpan SeekSpan { get; set; }
14 |
15 | public FrameSize FrameSize { get; set; }
16 |
17 | ///
18 | /// The video/audio stream format. Set empty/null to let the ffmpeg guess that.
19 | /// rawvideo by default
20 | ///
21 | public string OutputFormat { get; set; }
22 |
23 | ///
24 | /// The pixel format. Set empty/null to let the ffmpeg guess that.
25 | ///
26 | public string PixelFormat { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/FfTaskBase.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using MediaToolkit.Core;
4 | using MediaToolkit.Services;
5 |
6 | namespace MediaToolkit.Tasks
7 | {
8 | ///
9 | /// Common interface for FF tasks.
10 | ///
11 | public abstract class FfTaskBase
12 | {
13 | ///
14 | /// Override to create the call parameters.
15 | ///
16 | public abstract IList CreateArguments();
17 |
18 | ///
19 | /// Override to execute the command.
20 | ///
21 | public abstract Task ExecuteCommandAsync(IFfProcess ffProcess);
22 |
23 | ///
24 | /// Internal method for execution with the service instance.
25 | ///
26 | internal abstract Task ExecuteAsync(MediaToolkitService ffService);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/MediaToolkit.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | MediaToolkit.Test
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/MediaToolkit/Model/Disposition.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace MediaToolkit.Model
4 | {
5 | public class Disposition
6 | {
7 | public int Default { get; set; }
8 | public int Dub { get; set; }
9 | public int Original { get; set; }
10 | public int Comment { get; set; }
11 | public int Lyrics { get; set; }
12 | public int Karaoke { get; set; }
13 | public int Forced { get; set; }
14 |
15 | [JsonPropertyName("hearing_impaired")]
16 | public int HearingImpaired { get; set; }
17 |
18 | [JsonPropertyName("visual_impaired")]
19 | public int VisualImpaired { get; set; }
20 |
21 | [JsonPropertyName("clean_effects")]
22 | public int CleanEffects { get; set; }
23 |
24 | [JsonPropertyName("attached_pic")]
25 | public int AttachedPic { get; set; }
26 |
27 | [JsonPropertyName("timed_thumbnails")]
28 | public int TimedThumbnails { get; set; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/MediaToolkit/Core/StreamReaderWrapper.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Threading.Tasks;
3 | using Medallion.Shell.Streams;
4 |
5 | namespace MediaToolkit.Core
6 | {
7 | ///
8 | /// Provides stream reader interfaces
9 | ///
10 | internal class StreamReaderWrapper : IProcessStreamReader
11 | {
12 | private ProcessStreamReader _streamReader;
13 |
14 | ///
15 | /// Ctor.
16 | ///
17 | public StreamReaderWrapper(ProcessStreamReader streamReader)
18 | {
19 | this._streamReader = streamReader;
20 | }
21 |
22 | ///
23 | /// IProcessStreamReader
24 | ///
25 | public Stream BaseStream => this._streamReader.BaseStream;
26 |
27 | ///
28 | /// IProcessStreamReader
29 | ///
30 | public Task ReadToEndAsync()
31 | {
32 | return this._streamReader.ReadToEndAsync();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/MediaToolkit/Model/Metadata.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MediaToolkit.Model
4 | {
5 | public class Metadata
6 | {
7 | internal Metadata() { }
8 | public TimeSpan Duration { get; internal set; }
9 | public Video VideoData { get; internal set; }
10 | public Audio AudioData { get; internal set; }
11 |
12 | public class Video
13 | {
14 | internal Video() { }
15 | public string Format { get; internal set; }
16 | public string ColorModel { get; internal set; }
17 | public string FrameSize { get; internal set; }
18 | public int? BitRateKbs { get; internal set; }
19 | public double Fps { get; internal set; }
20 | }
21 |
22 | public class Audio
23 | {
24 | internal Audio() { }
25 |
26 | public string Format { get; internal set; }
27 | public string SampleRate { get; internal set; }
28 | public string ChannelOutput { get; internal set; }
29 | public int BitRateKbs { get; internal set; }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/EngineTest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO.Abstractions.TestingHelpers;
3 | using Xunit;
4 |
5 | namespace MediaToolkit.Test
6 | {
7 | public class EngineTest
8 | {
9 | ///
10 | /// Path to ffprobe.exe should be composed using the same directory as ffmpeg
11 | ///
12 | [Fact]
13 | public void Should_Initialize_FFprobe_Path()
14 | {
15 | // Create SUT directly intentionally
16 | var fileSystem = new MockFileSystem(new Dictionary
17 | {
18 | {@"c:\some\folder\path\ffmpeg.exe", new MockFileData("")},
19 | {@"c:\some\folder\path\ffprobe.exe", new MockFileData("")}
20 | });
21 |
22 | var engine = new Engine(@"c:\some\folder\path\ffmpeg.exe", fileSystem);
23 |
24 | Assert.Equal(@"c:\some\folder\path\ffmpeg.exe", engine.FfmpegFilePath);
25 | Assert.Equal(@"c:\some\folder\path\ffprobe.exe", engine.FfprobeFilePath);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/Util/ExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using MediaToolkit.Util;
4 | using Xunit;
5 |
6 | namespace MediaToolkit.Test.Util
7 | {
8 | public class ExtensionTest
9 | {
10 | public class ForEach
11 | {
12 | public ForEach()
13 | {
14 | this.CollectionUnderTest = new[] { "Foo", "Bar" };
15 | }
16 |
17 | public IEnumerable CollectionUnderTest;
18 |
19 | [Fact]
20 | public void Will_Iterate_Through_EachItem_InCollection()
21 | {
22 | int expectedIterations = 2;
23 | int iterations = 0;
24 |
25 | this.CollectionUnderTest.ForEach(item => iterations++);
26 |
27 | Assert.Equal(expectedIterations, iterations);
28 | }
29 |
30 | [Fact]
31 | public void When_ActionIsNull_Throw_ArgumentNullException()
32 | {
33 | Assert.Throws(() =>
34 | {
35 | this.CollectionUnderTest.ForEach(null);
36 | });
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/MediaToolkit/Model/Format.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace MediaToolkit.Model
5 | {
6 | public class Format
7 | {
8 | public string Filename { get; set; }
9 |
10 | [JsonPropertyName("nb_streams")]
11 | public int NbStreams { get; set; }
12 |
13 | [JsonPropertyName("nb_programs")]
14 | public int NbPrograms { get; set; }
15 |
16 | [JsonPropertyName("format_name")]
17 | public string FormatName { get; set; }
18 |
19 | [JsonPropertyName("format_long_name")]
20 | public string FormatLongName { get; set; }
21 |
22 | [JsonPropertyName("start_time")]
23 | public string StartTime { get; set; }
24 |
25 | public string Duration { get; set; }
26 | public string Size { get; set; }
27 |
28 | [JsonPropertyName("bit_rate")]
29 | public string BitRate { get; set; }
30 |
31 | [JsonPropertyName("probe_score")]
32 | public int ProbeScore { get; set; }
33 |
34 | public Dictionary Tags { get; set; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/Core/FfProcessFactoryTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO.Abstractions.TestingHelpers;
4 | using MediaToolkit.Core;
5 | using MediaToolkit.Services;
6 | using Xunit;
7 |
8 | namespace MediaToolkit.Test.Core
9 | {
10 | public class FfProcessFactoryTest
11 | {
12 | [Fact]
13 | public void Should_Throw_If_Ffmpeg_Not_Found()
14 | {
15 | var options = new MediaToolkitOptions { FfMpegPath = @"c:\non_existing\ffmpeg.exe" };
16 | var mockFs = new MockFileSystem();
17 | Assert.Throws(() => new FfProcessFactory(options, mockFs));
18 | }
19 |
20 | [Fact]
21 | public void Should_Throw_If_Ffprobe_Not_Found()
22 | {
23 | var options = new MediaToolkitOptions { FfMpegPath = @"c:\existing\ffmpeg.exe" };
24 | var mockFs = new MockFileSystem(
25 | new Dictionary
26 | {
27 | { @"c:\existing\ffmpeg.exe", new MockFileData("abc") }
28 | });
29 |
30 | Assert.Throws(() => new FfProcessFactory(options, mockFs));
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) [2014] [Fatih Aydin]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/SampleApp/bin/Debug/netcoreapp3.1/SampleApp.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/SampleApp",
16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17 | "console": "internalConsole",
18 | "stopAtEntry": false
19 | },
20 | {
21 | "name": ".NET Core Attach",
22 | "type": "coreclr",
23 | "request": "attach",
24 | "processId": "${command:pickProcess}"
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/MediaToolkit/MediaToolkit.NetCore.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 2.0.0
5 | 2.0.0
6 |
7 |
8 |
9 | 0.2.1-preview
10 | https://github.com/mtebenev/MediaToolkit
11 | MIT
12 | Maxim Tebenev
13 | Maxim Tebenev, Fatih Aydin, 2018-2020
14 | MediaToolkit, Media, Video, Audio, converter, ffmpeg, flv, mp4, flash, h264, hd, 720p, 1080p, dvd
15 | false
16 | portable
17 | 2.0.0-*
18 | false
19 | false
20 | false
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/MediaToolkit/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO.Abstractions;
3 | using MediaToolkit.Core;
4 | using MediaToolkit.Services;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.DependencyInjection.Extensions;
7 |
8 | namespace MediaToolkit
9 | {
10 | public static class ServiceCollectionExtensions
11 | {
12 | ///
13 | /// Adds the MediaToolkit to the service collection.
14 | ///
15 | public static IServiceCollection AddMediaToolkit(this IServiceCollection services, string ffmpegFilePath, string ffprobeFilePath = null)
16 | {
17 | if(services == null)
18 | {
19 | throw new ArgumentNullException(nameof(services));
20 | }
21 |
22 | var options = new MediaToolkitOptions
23 | {
24 | FfMpegPath = ffmpegFilePath
25 | };
26 |
27 | if(!string.IsNullOrEmpty(ffprobeFilePath))
28 | {
29 | options.FfProbePath = ffprobeFilePath;
30 | }
31 |
32 | services.TryAddSingleton();
33 | services.AddSingleton(options);
34 | services.AddSingleton();
35 | services.AddSingleton();
36 |
37 | return services;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/MediaToolkit/Util/Document.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace MediaToolkit.Util
4 | {
5 | using System;
6 |
7 | public class Document
8 | {
9 | [Obsolete("Replaced by the method `MediaToolkit.Util.Document.IsLocked`")]
10 | internal static bool IsFileLocked(FileInfo file)
11 | {
12 | FileStream fileStream = null;
13 | try
14 | {
15 | fileStream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
16 | }
17 | catch(IOException)
18 | {
19 | return true;
20 | }
21 | finally
22 | {
23 | if(fileStream != null)
24 | fileStream.Close();
25 | }
26 |
27 | return false;
28 | }
29 |
30 |
31 | internal static bool IsLocked(string filePath)
32 | {
33 | if(filePath.IsNullOrWhiteSpace())
34 | {
35 | throw new ArgumentNullException("filePath");
36 | }
37 |
38 | FileInfo file = new FileInfo(filePath);
39 | FileStream fileStream = null;
40 |
41 | try
42 | {
43 | fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
44 | }
45 | catch(IOException)
46 | {
47 | return true;
48 | }
49 | finally
50 | {
51 | if(fileStream != null)
52 | fileStream.Close();
53 | }
54 |
55 | return false;
56 | }
57 |
58 |
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/SampleApp/SampleApp.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/SampleApp/SampleApp.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/SampleApp/SampleApp.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/MediaToolkit/ConvertProgressEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MediaToolkit
4 | {
5 | public class ConvertProgressEventArgs : EventArgs
6 | {
7 | ///
8 | /// Raises notifications on the conversion process
9 | ///
10 | /// Duration of the media which has been processed
11 | /// The total duration of the original media
12 | /// The specific frame the conversion process is on
13 | /// The frames converted per second
14 | /// The current size in Kb of the converted media
15 | /// The bit rate of the converted media
16 | public ConvertProgressEventArgs(TimeSpan processed, TimeSpan totalDuration, long? frame, double? fps, int? sizeKb,
17 | double? bitrate)
18 | {
19 | TotalDuration = totalDuration;
20 | ProcessedDuration = processed;
21 | Frame = frame;
22 | Fps = fps;
23 | SizeKb = sizeKb;
24 | Bitrate = bitrate;
25 | }
26 |
27 | public long? Frame { get; private set; }
28 | public double? Fps { get; private set; }
29 | public int? SizeKb { get; private set; }
30 | public TimeSpan ProcessedDuration { get; private set; }
31 | public double? Bitrate { get; private set; }
32 | public TimeSpan TotalDuration { get; internal set; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/MediaToolkit/ConversionCompleteEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MediaToolkit
4 | {
5 | public class ConversionCompleteEventArgs : EventArgs
6 | {
7 | ///
8 | /// Raises notification once conversion is complete
9 | ///
10 | /// Duration of the media which has been processed
11 | /// The total duration of the original media
12 | /// The specific frame the conversion process is on
13 | /// The frames converted per second
14 | /// The current size in Kb of the converted media
15 | /// The bit rate of the converted media
16 | public ConversionCompleteEventArgs(TimeSpan processed, TimeSpan totalDuration, long frame, double fps, int sizeKb,
17 | double? bitrate)
18 | {
19 | TotalDuration = totalDuration;
20 | ProcessedDuration = processed;
21 | Frame = frame;
22 | Fps = fps;
23 | SizeKb = sizeKb;
24 | Bitrate = bitrate;
25 | }
26 |
27 | public long Frame { get; private set; }
28 | public double Fps { get; private set; }
29 | public int SizeKb { get; private set; }
30 | public TimeSpan ProcessedDuration { get; private set; }
31 | public double? Bitrate { get; private set; }
32 | public TimeSpan TotalDuration { get; internal set; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/MediaToolkit/Options/ConversionEnums.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit.Options
2 | {
3 | public enum Target
4 | {
5 | Default,
6 | VCD,
7 | SVCD,
8 | DVD,
9 | DV,
10 | DV50
11 | }
12 |
13 | public enum TargetStandard
14 | {
15 | Default,
16 | PAL,
17 | NTSC,
18 | FILM
19 | }
20 |
21 | public enum AudioSampleRate
22 | {
23 | Default,
24 | Hz22050,
25 | Hz44100,
26 | Hz48000
27 | }
28 |
29 | public enum VideoAspectRatio
30 | {
31 | Default,
32 | R3_2,
33 | R4_3,
34 | R5_3,
35 | R5_4,
36 | R16_9,
37 | R16_10,
38 | R17_9
39 | }
40 |
41 | public enum VideoSize
42 | {
43 | Default,
44 | _16Cif,
45 | _2K,
46 | _2Kflat,
47 | _2Kscope,
48 | _4Cif,
49 | _4K,
50 | _4Kflat,
51 | _4Kscope,
52 | Cga,
53 | Cif,
54 | Ega,
55 | Film,
56 | Fwqvga,
57 | Hd1080,
58 | Hd480,
59 | Hd720,
60 | Hqvga,
61 | Hsxga,
62 | Hvga,
63 | Nhd,
64 | Ntsc,
65 | Ntsc_Film,
66 | Pal,
67 | Qcif,
68 | Qhd,
69 | Qntsc,
70 | Qpal,
71 | Qqvga,
72 | Qsxga,
73 | Qvga,
74 | Qxga,
75 | Sntsc,
76 | Spal,
77 | Sqcif,
78 | Svga,
79 | Sxga,
80 | Uxga,
81 | Vga,
82 | Whsxga,
83 | Whuxga,
84 | Woxga,
85 | Wqsxga,
86 | Wquxga,
87 | Wqvga,
88 | Wsxga,
89 | Wuxga,
90 | Wvga,
91 | Wxga,
92 | Xga,
93 | Custom
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/FfTaskGetMetadata.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text.Json;
3 | using System.Threading.Tasks;
4 | using MediaToolkit.Core;
5 | using MediaToolkit.Model;
6 |
7 | namespace MediaToolkit.Tasks
8 | {
9 | ///
10 | /// The task retrieves the file metadata using ffprobe.
11 | ///
12 | public class FfTaskGetMetadata : FfProbeTaskBase
13 | {
14 | private readonly string _filePath;
15 |
16 | public FfTaskGetMetadata(string filePath)
17 | {
18 | this._filePath = filePath;
19 | }
20 |
21 | public override IList CreateArguments()
22 | {
23 | var arguments = new[]
24 | {
25 | "-v",
26 | "quiet",
27 | "-print_format",
28 | "json",
29 | "-show_format",
30 | "-show_streams",
31 | this._filePath
32 | };
33 | return arguments;
34 | }
35 |
36 | public override async Task ExecuteCommandAsync(IFfProcess ffProcess)
37 | {
38 | await ffProcess.Task;
39 | var options = new JsonSerializerOptions
40 | {
41 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase
42 | };
43 | var output = await ffProcess.OutputReader.ReadToEndAsync();
44 | var ffProbeOutput = JsonSerializer.Deserialize(output, options);
45 | var result = new GetMetadataResult(ffProbeOutput);
46 | return result;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/Util/RegexEngineTest.cs:
--------------------------------------------------------------------------------
1 | using MediaToolkit.Model;
2 | using System;
3 | using System.Linq;
4 | using Xunit;
5 |
6 | namespace MediaToolkit.Test.Util
7 | {
8 | public class RegexEngineTest
9 | {
10 | [Fact]
11 | public void TestVideo_VideoStreamInformationWithoutFPS_IsIgnored()
12 | {
13 | // this throws on before the patch
14 |
15 | const string faultyData = @" Stream #0:1: Video: mjpeg, yuvj420p(pc), 200x198 [SAR 96:96 DAR 100:99], 90k tbr, 90k tbn, 90k tbc";
16 |
17 | var regexEngineType = typeof(Engine).Assembly.GetTypes()
18 | .Where(x => x.Name == "RegexEngine")
19 | .Single();
20 |
21 | var engineParametersType = typeof(Engine).Assembly.GetTypes()
22 | .Where(x => x.Name == "EngineParameters")
23 | .Single();
24 |
25 | var engineParameters = Activator.CreateInstance(engineParametersType);
26 | engineParametersType.GetProperty("InputFile", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(engineParameters, new MediaFile());
27 |
28 | var testMethod = regexEngineType.GetMethod("TestVideo", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
29 | testMethod.Invoke(null, new object[]
30 | {
31 | faultyData,
32 | engineParameters
33 | });
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/FfTaskSaveThumbnail.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using MediaToolkit.Core;
5 |
6 | namespace MediaToolkit.Tasks
7 | {
8 | ///
9 | /// The task saves the video thumbnail.
10 | /// The result is a dummy value.
11 | ///
12 | public class FfTaskSaveThumbnail : FfMpegTaskBase
13 | {
14 | private readonly string _inputFilePath;
15 | private readonly string _outputFilePath;
16 | private readonly TimeSpan _seekSpan;
17 |
18 | ///
19 | /// Ctor.
20 | ///
21 | /// Full path to the input video file.
22 | /// Full path to the output video file.
23 | /// The frame timespan.
24 | public FfTaskSaveThumbnail(string inputFilePath, string outputFilePath, TimeSpan seekSpan)
25 | {
26 | this._inputFilePath = inputFilePath;
27 | this._outputFilePath = outputFilePath;
28 | this._seekSpan = seekSpan;
29 | }
30 |
31 | ///
32 | /// FfTaskBase.
33 | ///
34 | public override IList CreateArguments()
35 | {
36 | var arguments = new[]
37 | {
38 | "-nostdin",
39 | "-y",
40 | "-loglevel",
41 | "info",
42 | "-ss",
43 | this._seekSpan.TotalSeconds.ToString(),
44 | "-i",
45 | $@"{this._inputFilePath}",
46 | "-vframes",
47 | "1",
48 | $@"{this._outputFilePath}",
49 | };
50 |
51 | return arguments;
52 | }
53 |
54 | ///
55 | /// FfTaskBase.
56 | ///
57 | public override async Task ExecuteCommandAsync(IFfProcess ffProcess)
58 | {
59 | await ffProcess.Task;
60 | return 0;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/MediaToolkit/EngineParameters.cs:
--------------------------------------------------------------------------------
1 | namespace MediaToolkit
2 | {
3 | using MediaToolkit.Model;
4 | using MediaToolkit.Options;
5 | using MediaToolkit.Util;
6 |
7 | /// -------------------------------------------------------------------------------------------------
8 | /// Configures the engine to perform the correct task.
9 | internal class EngineParameters
10 | {
11 | internal bool HasCustomArguments
12 | {
13 | get
14 | {
15 | return !this.CustomArguments.IsNullOrWhiteSpace();
16 | }
17 | }
18 |
19 | /// -------------------------------------------------------------------------------------------------
20 | /// Gets or sets options for controlling the conversion.
21 | /// Options that control the conversion.
22 | internal ConversionOptions ConversionOptions { get; set; }
23 |
24 | internal string CustomArguments { get; set; }
25 |
26 | /// -------------------------------------------------------------------------------------------------
27 | /// Gets or sets the input file.
28 | /// The input file.
29 | internal MediaFile InputFile { get; set; }
30 |
31 | /// -------------------------------------------------------------------------------------------------
32 | /// Gets or sets the output file.
33 | /// The output file.
34 | internal MediaFile OutputFile { get; set; }
35 |
36 | /// -------------------------------------------------------------------------------------------------
37 | /// Gets or sets the task.
38 | /// The task.
39 | internal FFmpegTask Task { get; set; }
40 | }
41 | }
--------------------------------------------------------------------------------
/MediaToolkit/Util/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.IO;
5 |
6 | namespace System.Runtime.CompilerServices
7 | {
8 | public class ExtensionAttribute : Attribute
9 | {
10 | }
11 | }
12 |
13 | namespace MediaToolkit.Util
14 | {
15 | public static class Extensions
16 | {
17 | private const int BUFF_SIZE = 16 * 1024;
18 |
19 | internal static void CopyTo(this Stream input, Stream output)
20 | {
21 | byte[] buffer = new byte[Extensions.BUFF_SIZE];
22 | int bytesRead;
23 |
24 | while((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
25 | { output.Write(buffer, 0, bytesRead); }
26 | }
27 |
28 | public static string FormatInvariant(this string value, params object[] args)
29 | {
30 | try
31 | {
32 | return value == null
33 | ? string.Empty
34 | : string.Format(CultureInfo.InvariantCulture, value, args);
35 | }
36 | catch(FormatException ex)
37 | {
38 | return value;
39 | }
40 | }
41 |
42 | internal static bool IsNullOrWhiteSpace(this string value)
43 | {
44 | return string.IsNullOrEmpty(value) || value.Trim()
45 | .Length == 0;
46 | }
47 |
48 | internal static string Remove(this Enum enumerable, string text)
49 | {
50 | return enumerable.ToString()
51 | .Replace(text, "");
52 | }
53 |
54 | internal static string ToLower(this Enum enumerable)
55 | {
56 | return enumerable.ToString()
57 | .ToLowerInvariant();
58 | }
59 |
60 | public static void ForEach(this IEnumerable collection, Action action)
61 | {
62 | if(action == null)
63 | throw new ArgumentNullException("action");
64 |
65 | foreach(T t in collection)
66 | action(t);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/Services/FfServiceTest.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using MediaToolkit.Core;
3 | using MediaToolkit.Services;
4 | using MediaToolkit.Tasks;
5 | using NSubstitute;
6 | using Xunit;
7 |
8 | namespace MediaToolkit.Test.Services
9 | {
10 | public class FfServiceTest
11 | {
12 | [Fact]
13 | public async Task Should_Start_FfProbe()
14 | {
15 | var mockFfProbeTask = Substitute.For>();
16 | var mockProcessFactory = Substitute.For();
17 | var mockFfProcess = Substitute.For();
18 |
19 | var mockArgs = new[]
20 | {
21 | "arg1",
22 | "arg2"
23 | };
24 | mockProcessFactory.LaunchFfProbe(mockArgs).Returns(mockFfProcess);
25 | mockFfProbeTask.CreateArguments().Returns(mockArgs);
26 |
27 | var service = new MediaToolkitService(mockProcessFactory);
28 | await service.ExecuteAsync(mockFfProbeTask);
29 |
30 | mockProcessFactory.Received().LaunchFfProbe(mockArgs);
31 | await mockFfProbeTask.Received().ExecuteCommandAsync(mockFfProcess);
32 | }
33 |
34 | [Fact]
35 | public async Task Should_Start_FfMpeg()
36 | {
37 | var mockFfMpegTask = Substitute.For>();
38 | var mockProcessFactory = Substitute.For();
39 | var mockFfProcess = Substitute.For();
40 |
41 | var mockArgs = new[]
42 | {
43 | "arg1",
44 | "arg2"
45 | };
46 | mockProcessFactory.LaunchFfMpeg(mockArgs).Returns(mockFfProcess);
47 | mockFfMpegTask.CreateArguments().Returns(mockArgs);
48 |
49 | var service = new MediaToolkitService(mockProcessFactory);
50 | await service.ExecuteAsync(mockFfMpegTask);
51 |
52 | mockProcessFactory.Received().LaunchFfMpeg(mockArgs);
53 | await mockFfMpegTask.Received().ExecuteCommandAsync(mockFfProcess);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SampleApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Runtime.InteropServices;
4 | using System.Text.Json;
5 | using System.Threading.Tasks;
6 | using MediaToolkit;
7 | using MediaToolkit.Services;
8 | using MediaToolkit.Tasks;
9 | using Microsoft.Extensions.DependencyInjection;
10 |
11 | namespace SampleApp
12 | {
13 | class Program
14 | {
15 | static async Task Main(string[] args)
16 | {
17 | // Note: this sample assumes that under Windows we run the app with Visual Studio.
18 | // Under Linux we use vscode
19 | string ffmpegFilePath = @"C:\ffmpeg\ffmpeg.exe";
20 | string ffprobeFilePath = null;
21 | var videoPath = Path.GetFullPath(@"..\..\..\..\MediaToolkit.Test\TestVideo\BigBunny.m4v");
22 | var thumbnailPath = Path.GetFullPath(@"..\..\..\..\MediaToolkit.Test\TestVideo\thumbnail.jpeg");
23 |
24 | if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
25 | {
26 | ffmpegFilePath = @"/usr/bin/ffmpeg";
27 | ffprobeFilePath = @"/usr/bin/ffprobe";
28 | videoPath = Path.GetFullPath(@"../MediaToolkit.Test/TestVideo/BigBunny.m4v");
29 | thumbnailPath = Path.GetFullPath(@"../MediaToolkit.Test/TestVideo/thumbnail.jpeg");
30 | }
31 |
32 | var serviceProvider = new ServiceCollection()
33 | .AddMediaToolkit(ffmpegFilePath, ffprobeFilePath)
34 | .BuildServiceProvider();
35 |
36 | // Get metadata
37 | var service = serviceProvider.GetService();
38 | var metadataTask = new FfTaskGetMetadata(videoPath);
39 | var metadataResult = await service.ExecuteAsync(metadataTask);
40 |
41 | Console.WriteLine("Get metadata: \n");
42 | Console.Write(JsonSerializer.Serialize(metadataResult.Metadata));
43 | Console.WriteLine("\n");
44 |
45 | Console.WriteLine("Save thumbnail: \n");
46 | var saveThumbnailTask = new FfTaskSaveThumbnail(videoPath, thumbnailPath, TimeSpan.FromSeconds(10));
47 | await service.ExecuteAsync(saveThumbnailTask);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/MediaToolkit/Core/FfProcessFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO.Abstractions;
4 | using MediaToolkit.Services;
5 |
6 | namespace MediaToolkit.Core
7 | {
8 | ///
9 | /// The process factory implementation.
10 | ///
11 | internal class FfProcessFactory : IffProcessFactory
12 | {
13 | private readonly string _ffprobeFilePath;
14 | private readonly string _ffmpegFilePath;
15 |
16 | public FfProcessFactory(MediaToolkitOptions options, IFileSystem fileSystem)
17 | {
18 | if(options == null || string.IsNullOrEmpty(options.FfMpegPath))
19 | {
20 | throw new ArgumentNullException(nameof(options.FfMpegPath));
21 | }
22 |
23 | this._ffmpegFilePath = options.FfMpegPath;
24 | var ffmpegDirectoryPath = fileSystem.FileInfo.FromFileName(options.FfMpegPath).DirectoryName;
25 | this._ffprobeFilePath = string.IsNullOrEmpty(options.FfProbePath)
26 | ? fileSystem.Path.Combine(ffmpegDirectoryPath, "ffprobe.exe")
27 | : options.FfProbePath;
28 |
29 | EnsureFFmpegFileExists(fileSystem);
30 | }
31 |
32 | public IFfProcess LaunchFfMpeg(IEnumerable arguments)
33 | {
34 | IFfProcess ffProcess = new FfProcess(this._ffmpegFilePath, arguments);
35 | return ffProcess;
36 | }
37 |
38 | public IFfProcess LaunchFfProbe(IEnumerable arguments)
39 | {
40 | IFfProcess ffProcess = new FfProcess(this._ffprobeFilePath, arguments);
41 | return ffProcess;
42 | }
43 |
44 | private void EnsureFFmpegFileExists(IFileSystem fileSystem)
45 | {
46 | if(!fileSystem.File.Exists(this._ffmpegFilePath))
47 | throw new InvalidOperationException("Unable to locate ffmpeg executable. Make sure it exists at path passed to the MediaToolkit");
48 |
49 | if(!fileSystem.File.Exists(this._ffprobeFilePath))
50 | throw new InvalidOperationException("Unable to locate ffprobe executable. Make sure it exists at path passed to the MediaToolkit");
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/MediaToolkit/Services/MediaToolkitService.cs:
--------------------------------------------------------------------------------
1 | using System.IO.Abstractions;
2 | using System.Threading.Tasks;
3 | using MediaToolkit.Core;
4 | using MediaToolkit.Tasks;
5 |
6 | namespace MediaToolkit.Services
7 | {
8 | ///
9 | /// The FF service implementation.
10 | ///
11 | public class MediaToolkitService : IMediaToolkitService
12 | {
13 | private readonly IffProcessFactory _processFactory;
14 |
15 | ///
16 | /// Ctor.
17 | ///
18 | public MediaToolkitService(IffProcessFactory processFactory)
19 | {
20 | this._processFactory = processFactory;
21 | }
22 |
23 | ///
24 | /// Factory method.
25 | ///
26 | public static IMediaToolkitService CreateInstance(string ffMpegPath)
27 | {
28 | var options = new MediaToolkitOptions
29 | {
30 | FfMpegPath = ffMpegPath
31 | };
32 | var fileSystem = new FileSystem();
33 | var ffProcessFactory = new FfProcessFactory(options, fileSystem);
34 | var result = new MediaToolkitService(ffProcessFactory);
35 | return result;
36 | }
37 |
38 | public Task ExecuteAsync(FfTaskBase task)
39 | {
40 | var result = task.ExecuteAsync(this);
41 | return result;
42 | }
43 |
44 | ///
45 | /// Dispatcher for ffprobe tasks.
46 | ///
47 | internal Task ExecuteAsync(FfProbeTaskBase task)
48 | {
49 | var arguments = task.CreateArguments();
50 | var ffProcess = this._processFactory.LaunchFfProbe(arguments);
51 |
52 | return task.ExecuteCommandAsync(ffProcess);
53 | }
54 |
55 | ///
56 | /// Dispatcher for ffmpeg tasks.
57 | ///
58 | internal Task ExecuteAsync(FfMpegTaskBase task)
59 | {
60 | var arguments = task.CreateArguments();
61 | var ffProcess = this._processFactory.LaunchFfMpeg(arguments);
62 |
63 | return task.ExecuteCommandAsync(ffProcess);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/MediaToolkit.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27428.2037
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaToolkit", "MediaToolkit\MediaToolkit.csproj", "{51DEB1EE-0562-409D-AA13-DA3F150215CC}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp", "SampleApp\SampleApp.csproj", "{2D44C304-860B-47C4-A385-2B9A1D6CAE15}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaToolkit.Test", "MediaToolkit.Test\MediaToolkit.Test.csproj", "{0767521D-0850-4221-80D0-0FC23ECE79D8}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {51DEB1EE-0562-409D-AA13-DA3F150215CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {51DEB1EE-0562-409D-AA13-DA3F150215CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {51DEB1EE-0562-409D-AA13-DA3F150215CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {51DEB1EE-0562-409D-AA13-DA3F150215CC}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {2D44C304-860B-47C4-A385-2B9A1D6CAE15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {2D44C304-860B-47C4-A385-2B9A1D6CAE15}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {2D44C304-860B-47C4-A385-2B9A1D6CAE15}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {2D44C304-860B-47C4-A385-2B9A1D6CAE15}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {0767521D-0850-4221-80D0-0FC23ECE79D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {0767521D-0850-4221-80D0-0FC23ECE79D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {0767521D-0850-4221-80D0-0FC23ECE79D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {0767521D-0850-4221-80D0-0FC23ECE79D8}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {0D751528-A742-421C-8227-17B62C7000B0}
36 | EndGlobalSection
37 | GlobalSection(CodealikeProperties) = postSolution
38 | SolutionGuid = 27b6e555-e1d3-4bc8-80b8-643a861a9917
39 | EndGlobalSection
40 | EndGlobal
41 |
--------------------------------------------------------------------------------
/MediaToolkit/Core/FfProcess.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Medallion.Shell;
5 |
6 | namespace MediaToolkit.Core
7 | {
8 | ///
9 | /// FF process implementation
10 | ///
11 | internal class FfProcess : IFfProcess
12 | {
13 | private Command _command;
14 | private readonly StreamReaderWrapper _outputReader;
15 | private readonly StreamReaderWrapper _errorReader;
16 |
17 | ///
18 | /// Ctor.
19 | ///
20 | public FfProcess(string ffToolPath, IEnumerable arguments)
21 | {
22 | this._command = Command.Run(
23 | ffToolPath,
24 | arguments,
25 | options =>
26 | {
27 | options.DisposeOnExit();
28 | });
29 | this._outputReader = new StreamReaderWrapper(this._command.StandardOutput);
30 | this._errorReader = new StreamReaderWrapper(this._command.StandardError);
31 |
32 | this.Task = Task.Run(async () =>
33 | {
34 | var commandResult = await this._command.Task;
35 | if(!commandResult.Success)
36 | {
37 | var error = this._command.StandardError.ReadToEnd();
38 | throw new InvalidOperationException(error);
39 | }
40 | });
41 | }
42 |
43 | ///
44 | /// IFfProcess.
45 | ///
46 | public Task Task { get; }
47 |
48 | ///
49 | /// IFfProcess.
50 | ///
51 | public IProcessStreamReader OutputReader => this._outputReader;
52 |
53 | ///
54 | /// IFfProcess.
55 | ///
56 | public IProcessStreamReader ErrorReader => this._errorReader;
57 |
58 | ///
59 | /// Use to read all the output stream with one call.
60 | ///
61 | public async Task ReadOutputToEndAsync()
62 | {
63 | await this.Task;
64 | var result = await this._command.StandardOutput.ReadToEndAsync();
65 | return result;
66 | }
67 |
68 | ///
69 | /// Use to read all the error stream with one call.
70 | ///
71 | public async Task ReadErrorToEndAsync()
72 | {
73 | await this.Task;
74 | var result = await this._command.StandardError.ReadToEndAsync();
75 | return result;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/MediaToolkit/Tasks/FfTaskGetThumbnail.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Threading.Tasks;
5 | using MediaToolkit.Core;
6 |
7 | namespace MediaToolkit.Tasks
8 | {
9 | ///
10 | /// The tasks extracts the video thumbnail.
11 | ///
12 | public class FfTaskGetThumbnail : FfMpegTaskBase
13 | {
14 | private readonly string _inputFilePath;
15 | private readonly GetThumbnailOptions _options;
16 |
17 | ///
18 | /// Ctor.
19 | ///
20 | /// Full path to the input video file.
21 | /// The task options.
22 | public FfTaskGetThumbnail(string inputFilePath, GetThumbnailOptions options)
23 | {
24 | this._inputFilePath = inputFilePath;
25 | this._options = options;
26 | }
27 |
28 | public override IList CreateArguments()
29 | {
30 | var arguments = new List
31 | {
32 | "-hide_banner",
33 | "-loglevel",
34 | "info",
35 | "-ss",
36 | this._options.SeekSpan.TotalSeconds.ToString(),
37 | "-i",
38 | $@"{this._inputFilePath}",
39 | "-t",
40 | "1"
41 | };
42 |
43 | arguments.Add("-f");
44 | arguments.Add(String.IsNullOrEmpty(this._options.OutputFormat)
45 | ? OutputFormat.RawVideo
46 | : this._options.OutputFormat);
47 |
48 | if(!String.IsNullOrEmpty(this._options.PixelFormat))
49 | {
50 | arguments.Add("-pix_fmt");
51 | arguments.Add(this._options.PixelFormat);
52 | }
53 |
54 | arguments.Add("-vframes");
55 | arguments.Add("1");
56 |
57 | if(this._options.FrameSize != null)
58 | {
59 | arguments.Add("-s");
60 | arguments.Add(this._options.FrameSize.ToString());
61 | }
62 |
63 | arguments.Add("-");
64 |
65 | return arguments;
66 | }
67 |
68 | public override async Task ExecuteCommandAsync(IFfProcess ffProcess)
69 | {
70 | await ffProcess.Task;
71 | byte[] thumbnailData;
72 | using(var ms = new MemoryStream())
73 | {
74 | await ffProcess.OutputReader.BaseStream.CopyToAsync(ms);
75 | thumbnailData = ms.ToArray();
76 | }
77 |
78 | return new GetThumbnailResult(thumbnailData);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/Tasks/FfTaskGetThumbnailTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using MediaToolkit.Core;
5 | using MediaToolkit.Tasks;
6 | using NSubstitute;
7 | using Xunit;
8 |
9 | namespace MediaToolkit.Test.Tasks
10 | {
11 | public class FfTaskGetThumbnailTest
12 | {
13 | [Fact]
14 | public void Should_Add_Frame_Size_Arguments()
15 | {
16 | var options = new GetThumbnailOptions
17 | {
18 | FrameSize = new FrameSize(100, 200),
19 | SeekSpan = TimeSpan.FromSeconds(15)
20 | };
21 |
22 | var task = new FfTaskGetThumbnail(@"c:\some\path", options);
23 | var arguments = task.CreateArguments();
24 |
25 | Assert.Contains("-s 100x200", String.Join(' ', arguments));
26 | }
27 |
28 | [Fact]
29 | public void Should_Add_Output_format_Arguments()
30 | {
31 | var options = new GetThumbnailOptions
32 | {
33 | OutputFormat = OutputFormat.Gif
34 | };
35 |
36 | var task = new FfTaskGetThumbnail(@"c:\some\path", options);
37 | var arguments = task.CreateArguments();
38 |
39 | Assert.Contains("-f gif", String.Join(' ', arguments));
40 | }
41 |
42 | [Fact]
43 | public void Should_Add_Pixel_format_Arguments()
44 | {
45 | var options = new GetThumbnailOptions
46 | {
47 | PixelFormat = PixelFormat.Gray
48 | };
49 |
50 | var task = new FfTaskGetThumbnail(@"c:\some\path", options);
51 | var arguments = task.CreateArguments();
52 |
53 | Assert.Contains("-pix_fmt gray", String.Join(' ', arguments));
54 | }
55 |
56 | [Fact]
57 | public void Should_Generate_Default_Arguments()
58 | {
59 | var task = new FfTaskGetThumbnail(@"c:\some\path", new GetThumbnailOptions());
60 | var arguments = task.CreateArguments();
61 |
62 | var args = String.Join(' ', arguments);
63 | Assert.Equal(@"-hide_banner -loglevel info -ss 0 -i c:\some\path -t 1 -f rawvideo -vframes 1 -", String.Join(' ', arguments));
64 | }
65 |
66 | [Fact]
67 | public async Task Should_Return_Output_As_Byte_Array()
68 | {
69 | var mockOutputReader = Substitute.For();
70 | var mockFfProcess = Substitute.For();
71 | mockFfProcess.OutputReader.Returns(mockOutputReader);
72 |
73 | GetThumbnailResult result;
74 | using(var ms = new MemoryStream(new byte[] { 10, 20, 30 }))
75 | {
76 | mockOutputReader.BaseStream.Returns(ms);
77 | var options = new GetThumbnailOptions
78 | {
79 | SeekSpan = TimeSpan.FromSeconds(15)
80 | };
81 | var task = new FfTaskGetThumbnail(@"c:\some\path", options);
82 | result = await task.ExecuteCommandAsync(mockFfProcess);
83 | }
84 |
85 | Assert.Equal(new byte[] { 10, 20, 30 }, result.ThumbnailData);
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/MediaToolkit/EngineBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO.Abstractions;
4 | using System.Threading;
5 | using MediaToolkit.Properties;
6 | using MediaToolkit.Util;
7 |
8 | namespace MediaToolkit
9 | {
10 | public class EngineBase : IDisposable
11 | {
12 | private bool isDisposed;
13 |
14 | /// Used for locking the FFmpeg process to one thread.
15 | private const string LockName = "MediaToolkit.Engine.LockName";
16 |
17 | private readonly string _ffprobeFilePath;
18 |
19 | private readonly IFileSystem _fileSystem;
20 |
21 | /// The Mutex.
22 | protected readonly Mutex Mutex;
23 |
24 | /// The ffmpeg process.
25 | protected Process FFmpegProcess;
26 |
27 | ///
28 | /// Initializes FFmpeg.exe; Ensuring that there is a copy in the clients temp folder & isn't in use by another process.
29 | /// Assumes that ffprobe located in the same directory as ffmpeg
30 | ///
31 | protected EngineBase(string ffMpegPath, IFileSystem fileSystem)
32 | {
33 | _fileSystem = fileSystem;
34 | Mutex = new Mutex(false, LockName);
35 | isDisposed = false;
36 |
37 | if(ffMpegPath.IsNullOrWhiteSpace())
38 | throw new ArgumentException(nameof(ffMpegPath));
39 |
40 | FfmpegFilePath = ffMpegPath;
41 | var ffmpegDirectoryPath = _fileSystem.FileInfo.FromFileName(ffMpegPath).DirectoryName;
42 | FfprobeFilePath = _fileSystem.Path.Combine(ffmpegDirectoryPath, "ffprobe.exe");
43 |
44 | EnsureFFmpegFileExists();
45 | EnsureFFmpegIsNotUsed();
46 | }
47 |
48 | public string FfmpegFilePath { get; }
49 | public string FfprobeFilePath { get; }
50 |
51 | private void EnsureFFmpegIsNotUsed()
52 | {
53 | try
54 | {
55 | this.Mutex.WaitOne();
56 | Process.GetProcessesByName(Resources.FFmpegProcessName)
57 | .ForEach(process =>
58 | {
59 | process.Kill();
60 | process.WaitForExit();
61 | });
62 | }
63 | finally
64 | {
65 | this.Mutex.ReleaseMutex();
66 | }
67 | }
68 |
69 | private void EnsureFFmpegFileExists()
70 | {
71 | if(!_fileSystem.File.Exists(FfmpegFilePath))
72 | throw new InvalidOperationException("Unable to locate ffmpeg executable. Make sure it exists at path passed to Engine constructor");
73 |
74 | if(!_fileSystem.File.Exists(FfprobeFilePath))
75 | throw new InvalidOperationException("Unable to locate ffprobe executable. Make sure it exists at path passed to Engine constructor");
76 | }
77 |
78 |
79 | ///-------------------------------------------------------------------------------------------------
80 | ///
81 | /// Performs application-defined tasks associated with freeing, releasing, or resetting
82 | /// unmanaged resources.
83 | ///
84 | /// Aydin Aydin, 30/03/2015.
85 | public virtual void Dispose()
86 | {
87 | this.Dispose(true);
88 | }
89 |
90 | private void Dispose(bool disposing)
91 | {
92 | if(!disposing || this.isDisposed)
93 | {
94 | return;
95 | }
96 |
97 | if(FFmpegProcess != null)
98 | {
99 | this.FFmpegProcess.Dispose();
100 | }
101 |
102 | this.FFmpegProcess = null;
103 | this.isDisposed = true;
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/MediaToolkit/Options/ConversionOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MediaToolkit.Options
4 | {
5 | public class ConversionOptions
6 | {
7 | ///
8 | /// ---
9 | /// Cut audio / video from existing media
10 | /// Example: To cut a 15 minute section
11 | /// out of a 30 minute video starting
12 | /// from the 5th minute:
13 | /// The start position would be: TimeSpan.FromMinutes(5)
14 | /// The length would be: TimeSpan.FromMinutes(15)
15 | ///
16 | ///
17 | /// Specify the position to seek to,
18 | /// if you wish to begin the cut starting
19 | /// from the 5th minute, use: TimeSpan.FromMinutes(5);
20 | ///
21 | ///
22 | /// Specify the length of the video to cut,
23 | /// to cut out a 15 minute duration
24 | /// simply use: TimeSpan.FromMinutes(15);
25 | ///
26 | public void CutMedia(TimeSpan seekToPosition, TimeSpan length)
27 | {
28 | this.Seek = seekToPosition;
29 | this.MaxVideoDuration = length;
30 | }
31 |
32 | ///
33 | /// Audio bit rate
34 | ///
35 | public int? AudioBitRate = null;
36 |
37 | ///
38 | /// Audio sample rate
39 | ///
40 | public AudioSampleRate AudioSampleRate = AudioSampleRate.Default;
41 |
42 | ///
43 | /// The maximum duration
44 | ///
45 | public TimeSpan? MaxVideoDuration = null;
46 |
47 | ///
48 | /// The frame to begin seeking from.
49 | ///
50 | public TimeSpan? Seek = null;
51 |
52 | ///
53 | /// Predefined audio and video options for various file formats,
54 | /// Can be used in conjunction with option
55 | ///
56 | public Target Target = Target.Default;
57 |
58 | ///
59 | /// Predefined standards to be used with the option
60 | ///
61 | public TargetStandard TargetStandard = TargetStandard.Default;
62 |
63 | ///
64 | /// Video aspect ratios
65 | ///
66 | public VideoAspectRatio VideoAspectRatio = VideoAspectRatio.Default;
67 |
68 | ///
69 | /// Video bit rate in kbit/s
70 | ///
71 | public int? VideoBitRate = null;
72 |
73 | ///
74 | /// Video frame rate
75 | ///
76 | public int? VideoFps = null;
77 |
78 | ///
79 | /// Video sizes
80 | ///
81 | public VideoSize VideoSize = VideoSize.Default;
82 |
83 | ///
84 | /// Custom Width when VideoSize.Custom is set
85 | ///
86 | public int? CustomWidth { get; set; }
87 |
88 | ///
89 | /// Custom Height when VideoSize.Custom is set
90 | ///
91 | public int? CustomHeight { get; set; }
92 |
93 | ///
94 | /// Specifies an optional rectangle from the source video to crop
95 | ///
96 | public CropRectangle SourceCrop { get; set; }
97 |
98 | ///
99 | /// Specifies wheter or not to use H.264 Baseline Profile
100 | ///
101 | public bool BaselineProfile { get; set; }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/MediaToolkit/Model/MediaStream.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace MediaToolkit.Model
5 | {
6 | public class MediaStream
7 | {
8 | public int Index { get; set; }
9 |
10 | [JsonPropertyName("codec_name")]
11 | public string CodecName { get; set; }
12 |
13 | [JsonPropertyName("codec_long_name")]
14 | public string CodecLongName { get; set; }
15 | public string Profile { get; set; }
16 |
17 | [JsonPropertyName("codec_type")]
18 | public string CodecType { get; set; }
19 |
20 | [JsonPropertyName("codec_time_base")]
21 | public string CodecTimeBase { get; set; }
22 |
23 | [JsonPropertyName("codec_tag_string")]
24 | public string CodecTagString { get; set; }
25 |
26 | [JsonPropertyName("codec_tag")]
27 | public string CodecTag { get; set; }
28 | public int Width { get; set; }
29 | public int Height { get; set; }
30 |
31 | [JsonPropertyName("coded_width")]
32 | public int CodedWidth { get; set; }
33 |
34 | [JsonPropertyName("coded_height")]
35 | public int CodedHeight { get; set; }
36 |
37 | [JsonPropertyName("has_b_frames")]
38 | public int HasBFrames { get; set; }
39 |
40 | [JsonPropertyName("sample_aspect_ratio")]
41 | public string SampleAspectRatio { get; set; }
42 |
43 | [JsonPropertyName("display_aspect_ratio")]
44 | public string DisplayAspectRatio { get; set; }
45 |
46 | [JsonPropertyName("pix_fmt")]
47 | public string PixFmt { get; set; }
48 |
49 | public int Level { get; set; }
50 |
51 | [JsonPropertyName("chroma_location")]
52 | public string ChromaLocation { get; set; }
53 |
54 | public int Refs { get; set; }
55 |
56 | [JsonPropertyName("is_avc")]
57 | public string IsAvc { get; set; }
58 |
59 | [JsonPropertyName("nal_length_size")]
60 | public string NalLengthSize { get; set; }
61 |
62 | [JsonPropertyName("r_frame_rate")]
63 | public string RFrameRate { get; set; }
64 |
65 | [JsonPropertyName("avg_frame_rate")]
66 | public string AvgFrameRate { get; set; }
67 |
68 | [JsonPropertyName("time_base")]
69 | public string TimeBase { get; set; }
70 |
71 | [JsonPropertyName("start_pts")]
72 | public int StartPts { get; set; }
73 |
74 | [JsonPropertyName("start_time")]
75 | public string StartTime { get; set; }
76 |
77 | [JsonPropertyName("duration_ts")]
78 | public int DurationTs { get; set; }
79 | public string Duration { get; set; }
80 |
81 | [JsonPropertyName("bit_rate")]
82 | public string BitRate { get; set; }
83 |
84 | [JsonPropertyName("bits_per_raw_sample")]
85 | public string BitsPerRawSample { get; set; }
86 |
87 | [JsonPropertyName("nb_frames")]
88 | public string NbFrames { get; set; }
89 |
90 | public Disposition Disposition { get; set; }
91 |
92 | [JsonPropertyName("sample_fmt")]
93 | public string SampleFmt { get; set; }
94 |
95 | [JsonPropertyName("sample_rate")]
96 | public string SampleRate { get; set; }
97 |
98 | public int? Channels { get; set; }
99 |
100 | [JsonPropertyName("channel_layout")]
101 | public string ChannelLayout { get; set; }
102 |
103 | [JsonPropertyName("bits_per_sample")]
104 | public int? BitsPerSample { get; set; }
105 |
106 | [JsonPropertyName("max_bit_rate")]
107 | public string MaxBitRate { get; set; }
108 |
109 | [JsonPropertyName("color_range")]
110 | public string ColorRange { get; set; }
111 |
112 | [JsonPropertyName("color_space")]
113 | public string ColorSpace { get; set; }
114 |
115 | [JsonPropertyName("color_transfer")]
116 | public string ColorTransfer { get; set; }
117 |
118 | [JsonPropertyName("color_primaries")]
119 | public string ColorPrimaries { get; set; }
120 |
121 | public Dictionary Tags { get; set; }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/MediaToolkit/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace MediaToolkit.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MediaToolkit.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to Input file not found.
65 | ///
66 | internal static string Exception_Media_Input_File_Not_Found {
67 | get {
68 | return ResourceManager.GetString("Exception_Media_Input_File_Not_Found", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to FFmpeg process is not running..
74 | ///
75 | internal static string Exceptions_FFmpeg_Process_Not_Running {
76 | get {
77 | return ResourceManager.GetString("Exceptions_FFmpeg_Process_Not_Running", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to FFMpeg GZip stream is null.
83 | ///
84 | internal static string Exceptions_Null_FFmpeg_Gzip_Stream {
85 | get {
86 | return ResourceManager.GetString("Exceptions_Null_FFmpeg_Gzip_Stream", resourceCulture);
87 | }
88 | }
89 |
90 | ///
91 | /// Looks up a localized string similar to MediaToolkit.Resources.FFmpeg.exe.gz.
92 | ///
93 | internal static string FFmpegManifestResourceName {
94 | get {
95 | return ResourceManager.GetString("FFmpegManifestResourceName", resourceCulture);
96 | }
97 | }
98 |
99 | ///
100 | /// Looks up a localized string similar to ffmpeg.
101 | ///
102 | internal static string FFmpegProcessName {
103 | get {
104 | return ResourceManager.GetString("FFmpegProcessName", resourceCulture);
105 | }
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/MediaToolkit/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | text/microsoft-resx
91 |
92 |
93 | 1.3
94 |
95 |
96 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
97 |
98 |
99 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
100 |
101 |
102 | MediaToolkit.Resources.FFmpeg.exe.gz
103 |
104 |
105 | FFMpeg GZip stream is null
106 |
107 |
108 | FFmpeg process is not running.
109 |
110 |
111 | ffmpeg
112 |
113 |
114 | Input file not found
115 |
116 |
--------------------------------------------------------------------------------
/MediaToolkit/CommandBuilder.cs:
--------------------------------------------------------------------------------
1 | using MediaToolkit.Model;
2 | using MediaToolkit.Options;
3 | using MediaToolkit.Util;
4 | using System;
5 | using System.Globalization;
6 | using System.Text;
7 |
8 | namespace MediaToolkit
9 | {
10 | internal class CommandBuilder
11 | {
12 | internal static string Serialize(EngineParameters engineParameters)
13 | {
14 | switch(engineParameters.Task)
15 | {
16 | case FFmpegTask.Convert:
17 | return Convert(engineParameters.InputFile, engineParameters.OutputFile, engineParameters.ConversionOptions);
18 |
19 | case FFmpegTask.GetMetaData:
20 | return GetMetadata(engineParameters.InputFile);
21 |
22 | case FFmpegTask.GetThumbnail:
23 | return GetThumbnail(engineParameters.InputFile, engineParameters.OutputFile, engineParameters.ConversionOptions);
24 | default:
25 | throw new ArgumentOutOfRangeException();
26 | }
27 | }
28 |
29 | private static string GetMetadata(MediaFile inputFile)
30 | {
31 | return string.Format("-i \"{0}\" ", inputFile.Filename);
32 | }
33 |
34 | private static string GetThumbnail(MediaFile inputFile, MediaFile outputFile, ConversionOptions conversionOptions)
35 | {
36 | var commandBuilder = new StringBuilder();
37 |
38 | commandBuilder.AppendFormat(CultureInfo.InvariantCulture, " -ss {0} ", conversionOptions.Seek.GetValueOrDefault(TimeSpan.FromSeconds(1)).TotalSeconds);
39 |
40 | commandBuilder.AppendFormat(" -i \"{0}\" ", inputFile.Filename);
41 | commandBuilder.AppendFormat(" -vframes {0} ", 1);
42 |
43 | return commandBuilder.AppendFormat(" \"{0}\" ", outputFile.Filename).ToString();
44 | }
45 |
46 | private static string Convert(MediaFile inputFile, MediaFile outputFile, ConversionOptions conversionOptions)
47 | {
48 | var commandBuilder = new StringBuilder();
49 |
50 | // Default conversion
51 | if(conversionOptions == null)
52 | return commandBuilder.AppendFormat(" -i \"{0}\" \"{1}\" ", inputFile.Filename, outputFile.Filename).ToString();
53 |
54 | // Media seek position
55 | if(conversionOptions.Seek != null)
56 | commandBuilder.AppendFormat(CultureInfo.InvariantCulture, " -ss {0} ", conversionOptions.Seek.Value.TotalSeconds);
57 |
58 | commandBuilder.AppendFormat(" -i \"{0}\" ", inputFile.Filename);
59 |
60 | // Physical media conversion (DVD etc)
61 | if(conversionOptions.Target != Target.Default)
62 | {
63 | commandBuilder.Append(" -target ");
64 | if(conversionOptions.TargetStandard != TargetStandard.Default)
65 | {
66 | commandBuilder.AppendFormat(" {0}-{1} \"{2}\" ", conversionOptions.TargetStandard.ToLower(), conversionOptions.Target.ToLower(), outputFile.Filename);
67 |
68 | return commandBuilder.ToString();
69 | }
70 | commandBuilder.AppendFormat("{0} \"{1}\" ", conversionOptions.Target.ToLower(), outputFile.Filename);
71 |
72 | return commandBuilder.ToString();
73 | }
74 |
75 | // Audio bit rate
76 | if(conversionOptions.AudioBitRate != null)
77 | commandBuilder.AppendFormat(" -ab {0}k", conversionOptions.AudioBitRate);
78 |
79 | // Audio sample rate
80 | if(conversionOptions.AudioSampleRate != AudioSampleRate.Default)
81 | commandBuilder.AppendFormat(" -ar {0} ", conversionOptions.AudioSampleRate.Remove("Hz"));
82 |
83 | // Maximum video duration
84 | if(conversionOptions.MaxVideoDuration != null)
85 | commandBuilder.AppendFormat(" -t {0} ", conversionOptions.MaxVideoDuration);
86 |
87 | // Video bit rate
88 | if(conversionOptions.VideoBitRate != null)
89 | commandBuilder.AppendFormat(" -b {0}k ", conversionOptions.VideoBitRate);
90 |
91 | // Video frame rate
92 | if(conversionOptions.VideoFps != null)
93 | commandBuilder.AppendFormat(" -r {0} ", conversionOptions.VideoFps);
94 |
95 | // Video size / resolution
96 | if(conversionOptions.VideoSize == VideoSize.Custom)
97 | {
98 | commandBuilder.AppendFormat(" -vf \"scale={0}:{1}\" ", conversionOptions.CustomWidth ?? -2, conversionOptions.CustomHeight ?? -2);
99 | }
100 | else if(conversionOptions.VideoSize != VideoSize.Default)
101 | {
102 | string size = conversionOptions.VideoSize.ToLower();
103 | if(size.StartsWith("_"))
104 | size = size.Replace("_", "");
105 | if(size.Contains("_"))
106 | size = size.Replace("_", "-");
107 |
108 | commandBuilder.AppendFormat(" -s {0} ", size);
109 | }
110 |
111 | // Video aspect ratio
112 | if(conversionOptions.VideoAspectRatio != VideoAspectRatio.Default)
113 | {
114 | string ratio = conversionOptions.VideoAspectRatio.ToString();
115 | ratio = ratio.Substring(1);
116 | ratio = ratio.Replace("_", ":");
117 |
118 | commandBuilder.AppendFormat(" -aspect {0} ", ratio);
119 | }
120 |
121 | // Video cropping
122 | if(conversionOptions.SourceCrop != null)
123 | {
124 | var crop = conversionOptions.SourceCrop;
125 | commandBuilder.AppendFormat(" -filter:v \"crop={0}:{1}:{2}:{3}\" ", crop.Width, crop.Height, crop.X, crop.Y);
126 | }
127 |
128 | if(conversionOptions.BaselineProfile)
129 | {
130 | commandBuilder.Append(" -profile:v baseline ");
131 | }
132 |
133 | return commandBuilder.AppendFormat(" \"{0}\" ", outputFile.Filename).ToString();
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | dist/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # DNX
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 |
50 | *_i.c
51 | *_p.c
52 | *_i.h
53 | *.ilk
54 | *.meta
55 | *.obj
56 | *.pch
57 | *.pdb
58 | *.pgc
59 | *.pgd
60 | *.rsp
61 | *.sbr
62 | *.tlb
63 | *.tli
64 | *.tlh
65 | *.tmp
66 | *.tmp_proj
67 | *.log
68 | *.vspscc
69 | *.vssscc
70 | .builds
71 | *.pidb
72 | *.svclog
73 | *.scc
74 |
75 | # Chutzpah Test files
76 | _Chutzpah*
77 |
78 | # Visual C++ cache files
79 | ipch/
80 | *.aps
81 | *.ncb
82 | *.opendb
83 | *.opensdf
84 | *.sdf
85 | *.cachefile
86 | *.VC.db
87 | *.VC.VC.opendb
88 |
89 | # Visual Studio profiler
90 | *.psess
91 | *.vsp
92 | *.vspx
93 | *.sap
94 |
95 | # TFS 2012 Local Workspace
96 | $tf/
97 |
98 | # Guidance Automation Toolkit
99 | *.gpState
100 |
101 | # ReSharper is a .NET coding add-in
102 | _ReSharper*/
103 | *.[Rr]e[Ss]harper
104 | *.DotSettings.user
105 |
106 | # JustCode is a .NET coding add-in
107 | .JustCode
108 |
109 | # TeamCity is a build add-in
110 | _TeamCity*
111 |
112 | # DotCover is a Code Coverage Tool
113 | *.dotCover
114 |
115 | # NCrunch
116 | _NCrunch_*
117 | .*crunch*.local.xml
118 | nCrunchTemp_*
119 |
120 | # MightyMoose
121 | *.mm.*
122 | AutoTest.Net/
123 |
124 | # Web workbench (sass)
125 | .sass-cache/
126 |
127 | # Installshield output folder
128 | [Ee]xpress/
129 |
130 | # DocProject is a documentation generator add-in
131 | DocProject/buildhelp/
132 | DocProject/Help/*.HxT
133 | DocProject/Help/*.HxC
134 | DocProject/Help/*.hhc
135 | DocProject/Help/*.hhk
136 | DocProject/Help/*.hhp
137 | DocProject/Help/Html2
138 | DocProject/Help/html
139 |
140 | # Click-Once directory
141 | publish/
142 |
143 | # Publish Web Output
144 | *.[Pp]ublish.xml
145 | *.azurePubxml
146 | # TODO: Comment the next line if you want to checkin your web deploy settings
147 | # but database connection strings (with potential passwords) will be unencrypted
148 | #*.pubxml
149 | *.publishproj
150 |
151 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
152 | # checkin your Azure Web App publish settings, but sensitive information contained
153 | # in these scripts will be unencrypted
154 | PublishScripts/
155 |
156 | # NuGet Packages
157 | *.nupkg
158 | # The packages folder can be ignored because of Package Restore
159 | **/packages/*
160 | # except build/, which is used as an MSBuild target.
161 | !**/packages/build/
162 | # Uncomment if necessary however generally it will be regenerated when needed
163 | #!**/packages/repositories.config
164 | # NuGet v3's project.json files produces more ignoreable files
165 | *.nuget.props
166 | *.nuget.targets
167 |
168 | # Microsoft Azure Build Output
169 | csx/
170 | *.build.csdef
171 |
172 | # Microsoft Azure Emulator
173 | ecf/
174 | rcf/
175 |
176 | # Windows Store app package directories and files
177 | AppPackages/
178 | BundleArtifacts/
179 | Package.StoreAssociation.xml
180 | _pkginfo.txt
181 |
182 | # Visual Studio cache files
183 | # files ending in .cache can be ignored
184 | *.[Cc]ache
185 | # but keep track of directories ending in .cache
186 | !*.[Cc]ache/
187 |
188 | # Others
189 | ClientBin/
190 | ~$*
191 | *~
192 | *.dbmdl
193 | *.dbproj.schemaview
194 | *.jfm
195 | *.pfx
196 | *.publishsettings
197 | node_modules/
198 | orleans.codegen.cs
199 |
200 | # Since there are multiple workflows, uncomment next line to ignore bower_components
201 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
202 | #bower_components/
203 |
204 | # RIA/Silverlight projects
205 | Generated_Code/
206 |
207 | # Backup & report files from converting an old project file
208 | # to a newer Visual Studio version. Backup files are not needed,
209 | # because we have git ;-)
210 | _UpgradeReport_Files/
211 | Backup*/
212 | UpgradeLog*.XML
213 | UpgradeLog*.htm
214 |
215 | # SQL Server files
216 | *.mdf
217 | *.ldf
218 |
219 | # Business Intelligence projects
220 | *.rdl.data
221 | *.bim.layout
222 | *.bim_*.settings
223 |
224 | # Microsoft Fakes
225 | FakesAssemblies/
226 |
227 | # GhostDoc plugin setting file
228 | *.GhostDoc.xml
229 |
230 | # Node.js Tools for Visual Studio
231 | .ntvs_analysis.dat
232 |
233 | # Visual Studio 6 build log
234 | *.plg
235 |
236 | # Visual Studio 6 workspace options file
237 | *.opt
238 |
239 | # Visual Studio LightSwitch build output
240 | **/*.HTMLClient/GeneratedArtifacts
241 | **/*.DesktopClient/GeneratedArtifacts
242 | **/*.DesktopClient/ModelManifest.xml
243 | **/*.Server/GeneratedArtifacts
244 | **/*.Server/ModelManifest.xml
245 | _Pvt_Extensions
246 |
247 | # Paket dependency manager
248 | .paket/paket.exe
249 | paket-files/
250 |
251 | # FAKE - F# Make
252 | .fake/
253 |
254 | # JetBrains Rider
255 | .idea/
256 | *.sln.iml
257 |
258 | # CodeRush
259 | .cr/
260 |
261 | # Python Tools for Visual Studio (PTVS)
262 | __pycache__/
263 | *.pyc
264 | /config/appsettings.Azure.json
265 |
--------------------------------------------------------------------------------
/MediaToolkit/Util/RegexEngine.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using MediaToolkit.Model;
5 | using System.Globalization;
6 |
7 | namespace MediaToolkit.Util
8 | {
9 | ///
10 | /// Contains all Regex tasks
11 | ///
12 | internal static class RegexEngine
13 | {
14 | ///
15 | /// Dictionary containing every Regex test.
16 | ///
17 | internal static Dictionary Index = new Dictionary
18 | {
19 | {Find.BitRate, new Regex(@"([0-9]*)\s*kb/s")},
20 | {Find.Duration, new Regex(@"Duration: ([^,]*), ")},
21 | {Find.ConvertProgressFrame, new Regex(@"frame=\s*([0-9]*)")},
22 | {Find.ConvertProgressFps, new Regex(@"fps=\s*([0-9]*\.?[0-9]*?)")},
23 | {Find.ConvertProgressSize, new Regex(@"size=\s*([0-9]*)kB")},
24 | {Find.ConvertProgressFinished, new Regex(@"Lsize=\s*([0-9]*)kB")},
25 | {Find.ConvertProgressTime, new Regex(@"time=\s*([^ ]*)")},
26 | {Find.ConvertProgressBitrate, new Regex(@"bitrate=\s*([0-9]*\.?[0-9]*?)kbits/s")},
27 | {Find.MetaAudio, new Regex(@"(Stream\s*#[0-9]*:[0-9]*\(?[^\)]*?\)?: Audio:.*)")},
28 | {Find.AudioFormatHzChannel, new Regex(@"Audio:\s*([^,]*),\s([^,]*),\s([^,]*)")},
29 | {Find.MetaVideo, new Regex(@"(Stream\s*#[0-9]*:[0-9]*\(?[^\)]*?\)?: Video:.*)")},
30 | {
31 | Find.VideoFormatColorSize,
32 | new Regex(@"Video:\s*([^,]*),\s*([^,]*,?[^,]*?),?\s*(?=[0-9]*x[0-9]*)([0-9]*x[0-9]*)")
33 | },
34 | {Find.VideoFps, new Regex(@"([0-9\.]*)\s*tbr")}
35 | };
36 |
37 | ///
38 | /// ----
39 | /// Establishes whether the data contains progress information.
40 | ///
41 | /// Event data from the FFmpeg console.
42 | ///
43 | /// If successful, outputs a which is
44 | /// generated from the data.
45 | ///
46 | internal static bool IsProgressData(string data, out ConvertProgressEventArgs progressEventArgs)
47 | {
48 | progressEventArgs = null;
49 |
50 | Match matchFrame = Index[Find.ConvertProgressFrame].Match(data);
51 | Match matchFps = Index[Find.ConvertProgressFps].Match(data);
52 | Match matchSize = Index[Find.ConvertProgressSize].Match(data);
53 | Match matchTime = Index[Find.ConvertProgressTime].Match(data);
54 | Match matchBitrate = Index[Find.ConvertProgressBitrate].Match(data);
55 |
56 | if(!matchSize.Success || !matchTime.Success || !matchBitrate.Success)
57 | return false;
58 |
59 | TimeSpan processedDuration;
60 | TimeSpan.TryParse(matchTime.Groups[1].Value, out processedDuration);
61 |
62 | long? frame = GetLongValue(matchFrame);
63 | double? fps = GetDoubleValue(matchFps);
64 | int? sizeKb = GetIntValue(matchSize);
65 | double? bitrate = GetDoubleValue(matchBitrate);
66 |
67 | progressEventArgs = new ConvertProgressEventArgs(processedDuration, TimeSpan.Zero, frame, fps, sizeKb, bitrate);
68 |
69 | return true;
70 | }
71 |
72 | private static long? GetLongValue(Match match)
73 | {
74 | try
75 | {
76 | return Convert.ToInt64(match.Groups[1].Value, CultureInfo.InvariantCulture);
77 | }
78 | catch
79 | {
80 | return null;
81 | }
82 | }
83 |
84 | private static double? GetDoubleValue(Match match)
85 | {
86 | try
87 | {
88 | return Convert.ToDouble(match.Groups[1].Value, CultureInfo.InvariantCulture);
89 | }
90 | catch
91 | {
92 | return null;
93 | }
94 | }
95 |
96 | private static int? GetIntValue(Match match)
97 | {
98 | try
99 | {
100 | return Convert.ToInt32(match.Groups[1].Value, CultureInfo.InvariantCulture);
101 | }
102 | catch
103 | {
104 | return null;
105 | }
106 | }
107 |
108 |
109 | ///
110 | /// ----
111 | /// Establishes whether the data indicates the conversion is complete
112 | ///
113 | /// Event data from the FFmpeg console.
114 | ///
115 | /// If successful, outputs a which is
116 | /// generated from the data.
117 | ///
118 | internal static bool IsConvertCompleteData(string data, out ConversionCompleteEventArgs conversionCompleteEvent)
119 | {
120 | conversionCompleteEvent = null;
121 |
122 | Match matchFrame = Index[Find.ConvertProgressFrame].Match(data);
123 | Match matchFps = Index[Find.ConvertProgressFps].Match(data);
124 | Match matchFinished = Index[Find.ConvertProgressFinished].Match(data);
125 | Match matchTime = Index[Find.ConvertProgressTime].Match(data);
126 | Match matchBitrate = Index[Find.ConvertProgressBitrate].Match(data);
127 |
128 | if(!matchFrame.Success || !matchFps.Success || !matchFinished.Success || !matchTime.Success ||
129 | !matchBitrate.Success)
130 | return false;
131 |
132 | TimeSpan processedDuration;
133 | TimeSpan.TryParse(matchTime.Groups[1].Value, out processedDuration);
134 |
135 | long frame = Convert.ToInt64(matchFrame.Groups[1].Value, CultureInfo.InvariantCulture);
136 | double fps = Convert.ToDouble(matchFps.Groups[1].Value, CultureInfo.InvariantCulture);
137 | int sizeKb = Convert.ToInt32(matchFinished.Groups[1].Value, CultureInfo.InvariantCulture);
138 | double bitrate = Convert.ToDouble(matchBitrate.Groups[1].Value, CultureInfo.InvariantCulture);
139 |
140 | conversionCompleteEvent = new ConversionCompleteEventArgs(processedDuration, TimeSpan.Zero, frame, fps, sizeKb, bitrate);
141 |
142 | return true;
143 | }
144 |
145 | internal static void TestVideo(string data, EngineParameters engine)
146 | {
147 | Match matchMetaVideo = Index[Find.MetaVideo].Match(data);
148 |
149 | if(!matchMetaVideo.Success)
150 | return;
151 |
152 | string fullMetadata = matchMetaVideo.Groups[1].ToString();
153 |
154 | GroupCollection matchVideoFormatColorSize = Index[Find.VideoFormatColorSize].Match(fullMetadata).Groups;
155 | GroupCollection matchVideoFps = Index[Find.VideoFps].Match(fullMetadata).Groups;
156 | Match matchVideoBitRate = Index[Find.BitRate].Match(fullMetadata);
157 |
158 | if(engine.InputFile.Metadata == null)
159 | engine.InputFile.Metadata = new Metadata();
160 |
161 | if(engine.InputFile.Metadata.VideoData == null)
162 | engine.InputFile.Metadata.VideoData = new Metadata.Video
163 | {
164 | Format = matchVideoFormatColorSize[1].ToString(),
165 | ColorModel = matchVideoFormatColorSize[2].ToString(),
166 | FrameSize = matchVideoFormatColorSize[3].ToString(),
167 | Fps = matchVideoFps[1].Success && !string.IsNullOrEmpty(matchVideoFps[1].ToString()) ? Convert.ToDouble(matchVideoFps[1].ToString(), new CultureInfo("en-US")) : 0,
168 | BitRateKbs =
169 | matchVideoBitRate.Success
170 | ? (int?)Convert.ToInt32(matchVideoBitRate.Groups[1].ToString())
171 | : null
172 | };
173 | }
174 |
175 | internal static void TestAudio(string data, EngineParameters engine)
176 | {
177 | Match matchMetaAudio = Index[Find.MetaAudio].Match(data);
178 |
179 | if(!matchMetaAudio.Success)
180 | return;
181 |
182 | string fullMetadata = matchMetaAudio.Groups[1].ToString();
183 |
184 | GroupCollection matchAudioFormatHzChannel = Index[Find.AudioFormatHzChannel].Match(fullMetadata).Groups;
185 | GroupCollection matchAudioBitRate = Index[Find.BitRate].Match(fullMetadata).Groups;
186 |
187 | if(engine.InputFile.Metadata == null)
188 | engine.InputFile.Metadata = new Metadata();
189 |
190 | if(engine.InputFile.Metadata.AudioData == null)
191 | engine.InputFile.Metadata.AudioData = new Metadata.Audio
192 | {
193 | Format = matchAudioFormatHzChannel[1].ToString(),
194 | SampleRate = matchAudioFormatHzChannel[2].ToString(),
195 | ChannelOutput = matchAudioFormatHzChannel[3].ToString(),
196 | BitRateKbs = !(matchAudioBitRate[1].ToString().IsNullOrWhiteSpace()) ? Convert.ToInt32(matchAudioBitRate[1].ToString()) : 0
197 | };
198 | }
199 |
200 | internal enum Find
201 | {
202 | AudioFormatHzChannel,
203 | ConvertProgressBitrate,
204 | ConvertProgressFps,
205 | ConvertProgressFrame,
206 | ConvertProgressSize,
207 | ConvertProgressFinished,
208 | ConvertProgressTime,
209 | Duration,
210 | MetaAudio,
211 | MetaVideo,
212 | BitRate,
213 | VideoFormatColorSize,
214 | VideoFps
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MediaToolkit.NetCore
2 | ============
3 |
4 | This is port of MediaToolkit to .Net Core
5 | ---
6 |
7 | Notable changes are:
8 | 1. Projects are compiled with VS2019 using .Net Core
9 | 2. ffmpeg.exe is not embedded in library binaries, you should pass a path to ffmpeg.exe explicitly in constructor
10 |
11 | The API changes for 0.2.0 Preview
12 | ---
13 |
14 | Engine class becomes obsolete.
15 | The MediaToolkit now exposes injectable **IMediaToolkitService**.
16 |
17 | Instantiating the service
18 | ---
19 |
20 | Using .Net Core dependency injection:
21 | ```csharp
22 | var ffmpegFilePath = @"C:\ffmpeg\ffmpeg.exe";
23 | var serviceProvider = new ServiceCollection()
24 | .AddMediaToolkit(ffmpegFilePath)
25 | .BuildServiceProvider();
26 | ...
27 | var service = serviceProvider.GetService();
28 | ```
29 |
30 | Directly:
31 | ```csharp
32 | var ffmpegFilePath = @"C:\ffmpeg\ffmpeg.exe";
33 | var service = MediaToolkitService.CreateInstance(ffmpegFilePath);
34 | ```
35 |
36 | Getting the metadata
37 | ---
38 |
39 | This task uses ffprobe to extract the metadata.
40 | ```csharp
41 | var metadataTask = new FfTaskGetMetadata(videoPath);
42 | var metadataResult = await service.ExecuteAsync(metadataTask);
43 | ```
44 |
45 | Saving the thumbnail in a file
46 | ---
47 |
48 | ```csharp
49 | var saveThumbnailTask = new FfTaskSaveThumbnail(
50 | videoPath,
51 | thumbnailPath,
52 | TimeSpan.FromSeconds(10)
53 | );
54 | await service.ExecuteAsync(saveThumbnailTask);
55 | ```
56 |
57 | Getting the thumbnail data
58 | ---
59 | This task returns byte[] with the thumbnail data instead of saving it to a file.
60 | You can pass null to the frame size to let ffmpeg guess the frame dimensions.
61 |
62 | ```csharp
63 | var options = new GetThumbnailOptions
64 | {
65 | SeekSpan = TimeSpan.FromSeconds(10),
66 | OutputFormat = OutputFormat.Gif,
67 | PixelFormat = PixelFormat.Gray
68 | };
69 | var getThumbnailTask = new FfTaskGetThumbnail(
70 | videoPath,
71 | options
72 | );
73 | await service.ExecuteAsync(getThumbnailTask);
74 | ```
75 |
76 |
77 | ...From MediaToolkit (original)
78 | ---
79 |
80 | MediaToolkit provides a straightforward interface for handling media data, making tasks such as converting, slicing and editing both audio and video completely effortless.
81 |
82 | Under the hood, MediaToolkit is a .NET wrapper for FFmpeg; a free (LGPLv2.1) multimedia framework containing multiple audio and video codecs, supporting muxing, demuxing and transcoding tasks on many media formats.
83 |
84 | Contents
85 | ---------
86 |
87 | 1. [Features](#features)
88 | 2. [Get started!](#get-started)
89 | 3. [Samples](#samples)
90 | 4. [Licensing](#licensing)
91 |
92 | Features
93 | -------------
94 | - Resolving metadata
95 | - Generating thumbnails from videos
96 | - Transcode audio & video into other formats using parameters such as:
97 | - `Bit rate`
98 | - `Frame rate`
99 | - `Resolution`
100 | - `Aspect ratio`
101 | - `Seek position`
102 | - `Duration`
103 | - `Sample rate`
104 | - `Media format`
105 | - Convert media to physical formats and standards such as:
106 | - Standards include: `FILM`, `PAL` & `NTSC`
107 | - Mediums include: `DVD`, `DV`, `DV50`, `VCD` & `SVCD`
108 | - Supports custom FFmpeg command line arguments
109 | - Raising progress events
110 |
111 | Get started!
112 | ------------
113 | Install MediaToolkit from NuGet using the Package Manager Console with the following command (or search on [NuGet MediaToolkit](https://www.nuget.org/packages/MediaToolkit))
114 |
115 | PM> Install-Package MediaToolkit
116 |
117 | Samples
118 | -------
119 |
120 | - [Retrieve metadata](#retrieve-metadata)
121 | - [Perform basic video conversions](#basic-conversion)
122 | - [Grab thumbnail] (#grab-thumbnail-from-a-video)
123 | - [Convert from FLV to DVD](#convert-flash-video-to-dvd)
124 | - [Convert FLV to MP4 using various transcoding options](#transcoding-options-flv-to-mp4)
125 | - [Cut / split video] (#cut-video-down-to-smaller-length)
126 | - [Subscribing to events](#subscribe-to-events)
127 |
128 | ### Grab thumbnail from a video
129 |
130 | var inputFile = new MediaFile {Filename = @"C:\Path\To_Video.flv"};
131 | var outputFile = new MediaFile {Filename = @"C:\Path\To_Save_Image.jpg"};
132 |
133 | using (var engine = new Engine())
134 | {
135 | engine.GetMetadata(inputFile);
136 |
137 | // Saves the frame located on the 15th second of the video.
138 | var options = new ConversionOptions { Seek = TimeSpan.FromSeconds(15) };
139 | engine.GetThumbnail(inputFile, outputFile, options);
140 | }
141 |
142 | ### Retrieve metadata
143 |
144 | var inputFile = new MediaFile {Filename = @"C:\Path\To_Video.flv"};
145 |
146 | using (var engine = new Engine())
147 | {
148 | engine.GetMetadata(inputFile);
149 | }
150 |
151 | Console.WriteLine(inputFile.Metadata.Duration);
152 |
153 | ### Basic conversion
154 |
155 | var inputFile = new MediaFile {Filename = @"C:\Path\To_Video.flv"};
156 | var outputFile = new MediaFile {Filename = @"C:\Path\To_Save_New_Video.mp4"};
157 |
158 | using (var engine = new Engine())
159 | {
160 | engine.Convert(inputFile, outputFile);
161 | }
162 |
163 | ### Convert Flash video to DVD
164 |
165 | var inputFile = new MediaFile {Filename = @"C:\Path\To_Video.flv"};
166 | var outputFile = new MediaFile {Filename = @"C:\Path\To_Save_New_DVD.vob"};
167 |
168 | var conversionOptions = new ConversionOptions
169 | {
170 | Target = Target.DVD,
171 | TargetStandard = TargetStandard.PAL
172 | };
173 |
174 | using (var engine = new Engine())
175 | {
176 | engine.Convert(inputFile, outputFile, conversionOptions);
177 | }
178 |
179 | ### Transcoding options FLV to MP4
180 |
181 | var inputFile = new MediaFile {Filename = @"C:\Path\To_Video.flv"};
182 | var outputFile = new MediaFile {Filename = @"C:\Path\To_Save_New_Video.mp4"};
183 |
184 | var conversionOptions = new ConversionOptions
185 | {
186 | MaxVideoDuration = TimeSpan.FromSeconds(30),
187 | VideoAspectRatio = VideoAspectRatio.R16_9,
188 | VideoSize = VideoSize.Hd1080,
189 | AudioSampleRate = AudioSampleRate.Hz44100
190 | };
191 |
192 | using (var engine = new Engine())
193 | {
194 | engine.Convert(inputFile, outputFile, conversionOptions);
195 | }
196 |
197 | ### Cut video down to smaller length
198 |
199 | var inputFile = new MediaFile {Filename = @"C:\Path\To_Video.flv"};
200 | var outputFile = new MediaFile {Filename = @"C:\Path\To_Save_ExtractedVideo.flv"};
201 |
202 | using (var engine = new Engine())
203 | {
204 | engine.GetMetadata(inputFile);
205 |
206 | var options = new ConversionOptions();
207 |
208 | // This example will create a 25 second video, starting from the
209 | // 30th second of the original video.
210 | //// First parameter requests the starting frame to cut the media from.
211 | //// Second parameter requests how long to cut the video.
212 | options.CutMedia(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(25));
213 |
214 | engine.Convert(inputFile, outputFile, options);
215 | }
216 |
217 |
218 | ### Subscribe to events
219 |
220 | public void StartConverting()
221 | {
222 | var inputFile = new MediaFile {Filename = @"C:\Path\To_Video.flv"};
223 | var outputFile = new MediaFile {Filename = @"C:\Path\To_Save_New_Video.mp4"};
224 |
225 | using (var engine = new Engine())
226 | {
227 | engine.ConvertProgressEvent += ConvertProgressEvent;
228 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
229 | engine.Convert(inputFile, outputFile);
230 | }
231 | }
232 |
233 | private void ConvertProgressEvent(object sender, ConvertProgressEventArgs e)
234 | {
235 | Console.WriteLine("\n------------\nConverting...\n------------");
236 | Console.WriteLine("Bitrate: {0}", e.Bitrate);
237 | Console.WriteLine("Fps: {0}", e.Fps);
238 | Console.WriteLine("Frame: {0}", e.Frame);
239 | Console.WriteLine("ProcessedDuration: {0}", e.ProcessedDuration);
240 | Console.WriteLine("SizeKb: {0}", e.SizeKb);
241 | Console.WriteLine("TotalDuration: {0}\n", e.TotalDuration);
242 | }
243 |
244 | private void engine_ConversionCompleteEvent(object sender, ConversionCompleteEventArgs e)
245 | {
246 | Console.WriteLine("\n------------\nConversion complete!\n------------");
247 | Console.WriteLine("Bitrate: {0}", e.Bitrate);
248 | Console.WriteLine("Fps: {0}", e.Fps);
249 | Console.WriteLine("Frame: {0}", e.Frame);
250 | Console.WriteLine("ProcessedDuration: {0}", e.ProcessedDuration);
251 | Console.WriteLine("SizeKb: {0}", e.SizeKb);
252 | Console.WriteLine("TotalDuration: {0}\n", e.TotalDuration);
253 | }
254 |
255 |
256 | Licensing
257 | ---------
258 | - MediaToolkit is licensed under the [MIT license](https://github.com/AydinAdn/MediaToolkit/blob/master/LICENSE.md)
259 | - MediaToolkit uses [FFmpeg](http://ffmpeg.org), a multimedia framework which is licensed under the [LGPLv2.1 license](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html), its source can be downloaded from [here](https://github.com/AydinAdn/MediaToolkit/tree/master/FFmpeg%20src)
260 |
261 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/Tasks/FfTaskGetMetadataTest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using MediaToolkit.Core;
4 | using MediaToolkit.Tasks;
5 | using NSubstitute;
6 | using Xunit;
7 |
8 | namespace MediaToolkit.Test.Tasks
9 | {
10 | public class FfTaskGetMetadataTest
11 | {
12 | [Fact]
13 | public async Task Should_Deserialize_Format()
14 | {
15 | var mockOutputReader = Substitute.For();
16 | var mockFfProcess = Substitute.For();
17 | mockFfProcess.OutputReader.Returns(mockOutputReader);
18 |
19 | var json =
20 | @"{
21 | 'format': {
22 | 'filename': 'C:\\folder\\video.wmv',
23 | 'nb_streams': 2,
24 | 'nb_programs': 3,
25 | 'format_name': 'asf',
26 | 'format_long_name': 'ASF (Advanced / Active Streaming Format)',
27 | 'start_time': '0.000000',
28 | 'duration': '47.360000',
29 | 'size': '9964791',
30 | 'bit_rate': '1683241',
31 | 'probe_score': 100
32 | }
33 | }";
34 | mockOutputReader.ReadToEndAsync().Returns(json.Replace('\'', '\"'));
35 | var task = new FfTaskGetMetadata(@"som_path");
36 |
37 | var result = await task.ExecuteCommandAsync(mockFfProcess);
38 |
39 | // Verify
40 | var format = result.Metadata.Format;
41 | Assert.Equal(@"C:\folder\video.wmv", format.Filename);
42 | Assert.Equal(2, format.NbStreams);
43 | Assert.Equal(3, format.NbPrograms);
44 | Assert.Equal("asf", format.FormatName);
45 | Assert.Equal("ASF (Advanced / Active Streaming Format)", format.FormatLongName);
46 | Assert.Equal("0.000000", format.StartTime); // TODO: timespan
47 | Assert.Equal("47.360000", format.Duration); // TODO: timespan
48 | Assert.Equal("9964791", format.Size); // TOOD: long
49 | Assert.Equal("1683241", format.BitRate); // TOOD: int
50 | Assert.Equal(100, format.ProbeScore); // TOOD: int
51 | }
52 |
53 | [Fact]
54 | public async Task Should_Deserialize_Format_Tags()
55 | {
56 | var mockOutputReader = Substitute.For();
57 | var mockFfProcess = Substitute.For();
58 | mockFfProcess.OutputReader.Returns(mockOutputReader);
59 |
60 | var json =
61 | @"{
62 | 'format': {
63 | 'tags': {
64 | 'Application': 'Windows Movie Maker 6.0.6000.16386',
65 | 'WM/ToolVersion': '6.0.6000.16386',
66 | 'WM/ToolName': 'Windows Movie Maker',
67 | 'WMFSDKVersion': '11.0.6000.6346',
68 | 'WMFSDKNeeded': '0.0.0.0000',
69 | 'Buffer Average': '2688',
70 | 'VBR Peak': '1459825',
71 | 'IsVBR': '1',
72 | 'DeviceConformanceTemplate': 'MP@HL'
73 | }
74 | }
75 | }";
76 | mockOutputReader.ReadToEndAsync().Returns(json.Replace('\'', '\"'));
77 | var task = new FfTaskGetMetadata(@"som_path");
78 |
79 | var result = await task.ExecuteCommandAsync(mockFfProcess);
80 |
81 | // Verify
82 | var expectedTags = new Dictionary
83 | {
84 | { "Application", "Windows Movie Maker 6.0.6000.16386"},
85 | { "WM/ToolVersion", "6.0.6000.16386"},
86 | { "WM/ToolName", "Windows Movie Maker"},
87 | { "WMFSDKVersion", "11.0.6000.6346"},
88 | { "WMFSDKNeeded", "0.0.0.0000"},
89 | { "Buffer Average", "2688"},
90 | { "VBR Peak", "1459825"},
91 | { "IsVBR", "1"},
92 | { "DeviceConformanceTemplate", "MP@HL"}
93 | };
94 |
95 | Assert.Equal(expectedTags, result.Metadata.Format.Tags);
96 | }
97 |
98 | [Fact]
99 | public async Task Should_Deserialize_Video_Stream()
100 | {
101 | var mockOutputReader = Substitute.For();
102 | var mockFfProcess = Substitute.For();
103 | mockFfProcess.OutputReader.Returns(mockOutputReader);
104 |
105 | var json =
106 | @"{
107 | 'streams': [
108 | {
109 | 'index': 3,
110 | 'codec_name': 'h264',
111 | 'codec_long_name': 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10',
112 | 'profile': 'High',
113 | 'codec_type': 'video',
114 | 'codec_time_base': '21285403/1064160000',
115 | 'codec_tag_string': 'avc1',
116 | 'codec_tag': '0x31637661',
117 | 'width': 1280,
118 | 'height': 720,
119 | 'coded_width': 1280,
120 | 'coded_height': 720,
121 | 'has_b_frames': 2,
122 | 'sample_aspect_ratio': '1:1',
123 | 'display_aspect_ratio': '16:9',
124 | 'pix_fmt': 'yuv420p',
125 | 'level': 31,
126 | 'color_range': 'tv',
127 | 'color_space': 'bt709',
128 | 'color_transfer': 'bt709',
129 | 'color_primaries': 'bt709',
130 | 'chroma_location': 'left',
131 | 'refs': 1,
132 | 'is_avc': 'true',
133 | 'nal_length_size': '4',
134 | 'r_frame_rate': '25/1',
135 | 'avg_frame_rate': '532080000/21285403',
136 | 'time_base': '1/90000',
137 | 'start_pts': 11520,
138 | 'start_time': '0.128000',
139 | 'duration_ts': 85141612,
140 | 'duration': '946.017911',
141 | 'bit_rate': '1209472',
142 | 'bits_per_raw_sample': '8',
143 | 'nb_frames': '23648'
144 | }
145 | ]
146 | }";
147 | mockOutputReader.ReadToEndAsync().Returns(json.Replace('\'', '\"'));
148 | var task = new FfTaskGetMetadata(@"som_path");
149 |
150 | var result = await task.ExecuteCommandAsync(mockFfProcess);
151 |
152 | // Verify
153 | var stream = result.Metadata.Streams[0];
154 | Assert.Equal(3, stream.Index);
155 | Assert.Equal("h264", stream.CodecName);
156 | Assert.Equal("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", stream.CodecLongName);
157 | Assert.Equal("High", stream.Profile);
158 | Assert.Equal("video", stream.CodecType);
159 | Assert.Equal("21285403/1064160000", stream.CodecTimeBase);
160 | Assert.Equal("avc1", stream.CodecTagString);
161 | Assert.Equal("0x31637661", stream.CodecTag);
162 | Assert.Equal(1280, stream.Width);
163 | Assert.Equal(720, stream.Height);
164 | Assert.Equal(1280, stream.CodedWidth);
165 | Assert.Equal(720, stream.CodedHeight);
166 | Assert.Equal(2, stream.HasBFrames);
167 | Assert.Equal("1:1", stream.SampleAspectRatio);
168 | Assert.Equal("16:9", stream.DisplayAspectRatio);
169 | Assert.Equal("yuv420p", stream.PixFmt);
170 | Assert.Equal(31, stream.Level);
171 | Assert.Equal("tv", stream.ColorRange);
172 | Assert.Equal("bt709", stream.ColorSpace);
173 | Assert.Equal("bt709", stream.ColorTransfer);
174 | Assert.Equal("bt709", stream.ColorPrimaries);
175 | Assert.Equal("left", stream.ChromaLocation);
176 | Assert.Equal(1, stream.Refs);
177 | Assert.Equal("true", stream.IsAvc);
178 | Assert.Equal("4", stream.NalLengthSize);
179 | Assert.Equal("25/1", stream.RFrameRate);
180 | Assert.Equal("532080000/21285403", stream.AvgFrameRate);
181 | Assert.Equal("1/90000", stream.TimeBase);
182 | Assert.Equal(11520, stream.StartPts);
183 | Assert.Equal("0.128000", stream.StartTime);
184 | Assert.Equal(85141612, stream.DurationTs);
185 | Assert.Equal("946.017911", stream.Duration);
186 | Assert.Equal("1209472", stream.BitRate);
187 | Assert.Equal("8", stream.BitsPerRawSample);
188 | Assert.Equal("23648", stream.NbFrames);
189 | }
190 |
191 | [Fact]
192 | public async Task Should_Deserialize_Audio_Stream()
193 | {
194 | var mockOutputReader = Substitute.For();
195 | var mockFfProcess = Substitute.For();
196 | mockFfProcess.OutputReader.Returns(mockOutputReader);
197 |
198 | var json =
199 | @"{
200 | 'streams': [
201 | {
202 | 'sample_fmt': 'fltp',
203 | 'sample_rate': '48000',
204 | 'channels': 2,
205 | 'channel_layout': 'stereo',
206 | 'bits_per_sample': 11,
207 | 'bit_rate': '158337',
208 | 'max_bit_rate': '200000'
209 | }
210 | ]
211 | }";
212 | mockOutputReader.ReadToEndAsync().Returns(json.Replace('\'', '\"'));
213 | var task = new FfTaskGetMetadata(@"som_path");
214 |
215 | var result = await task.ExecuteCommandAsync(mockFfProcess);
216 |
217 | // Verify
218 | var stream = result.Metadata.Streams[0];
219 | Assert.Equal("fltp", stream.SampleFmt);
220 | Assert.Equal("48000", stream.SampleRate);
221 | Assert.Equal(2, stream.Channels);
222 | Assert.Equal("stereo", stream.ChannelLayout);
223 | Assert.Equal(11, stream.BitsPerSample);
224 | Assert.Equal("158337", stream.BitRate);
225 | Assert.Equal("200000", stream.MaxBitRate);
226 | }
227 |
228 | [Fact]
229 | public async Task Should_Deserialize_Stream_Disposition()
230 | {
231 | var mockOutputReader = Substitute.For();
232 | var mockFfProcess = Substitute.For();
233 | mockFfProcess.OutputReader.Returns(mockOutputReader);
234 |
235 | var json =
236 | @"{
237 | 'streams': [
238 | {
239 | 'disposition': {
240 | 'default': 1,
241 | 'dub': 2,
242 | 'original': 3,
243 | 'comment': 4,
244 | 'lyrics': 5,
245 | 'karaoke': 6,
246 | 'forced': 7,
247 | 'hearing_impaired': 8,
248 | 'visual_impaired': 9,
249 | 'clean_effects': 2,
250 | 'attached_pic': 3,
251 | 'timed_thumbnails': 4
252 | }
253 | }
254 | ]
255 | }";
256 | mockOutputReader.ReadToEndAsync().Returns(json.Replace('\'', '\"'));
257 | var task = new FfTaskGetMetadata(@"som_path");
258 |
259 | var result = await task.ExecuteCommandAsync(mockFfProcess);
260 |
261 | // Verify
262 | var disposition = result.Metadata.Streams[0].Disposition;
263 | Assert.Equal(1, disposition.Default);
264 | Assert.Equal(2, disposition.Dub);
265 | Assert.Equal(3, disposition.Original);
266 | Assert.Equal(4, disposition.Comment);
267 | Assert.Equal(5, disposition.Lyrics);
268 | Assert.Equal(6, disposition.Karaoke);
269 | Assert.Equal(7, disposition.Forced);
270 | Assert.Equal(8, disposition.HearingImpaired);
271 | Assert.Equal(9, disposition.VisualImpaired);
272 | Assert.Equal(2, disposition.CleanEffects);
273 | Assert.Equal(3, disposition.AttachedPic);
274 | Assert.Equal(4, disposition.TimedThumbnails);
275 | }
276 |
277 | [Fact]
278 | public async Task Should_Deserialize_Stream_Tags()
279 | {
280 | var mockOutputReader = Substitute.For();
281 | var mockFfProcess = Substitute.For();
282 | mockFfProcess.OutputReader.Returns(mockOutputReader);
283 |
284 | var json =
285 | @"{
286 | 'streams': [
287 | {
288 | 'tags': {
289 | 'language': 'eng',
290 | 'handler_name': 'Stereo'
291 | }
292 | }
293 | ]
294 | }";
295 | mockOutputReader.ReadToEndAsync().Returns(json.Replace('\'', '\"'));
296 | var task = new FfTaskGetMetadata(@"som_path");
297 |
298 | var result = await task.ExecuteCommandAsync(mockFfProcess);
299 |
300 | // Verify
301 | var expectedTags = new Dictionary
302 | {
303 | { "language", "eng"},
304 | { "handler_name", "Stereo"}
305 | };
306 |
307 | Assert.Equal(expectedTags, result.Metadata.Streams[0].Tags);
308 | }
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/MediaToolkit/Engine.cs:
--------------------------------------------------------------------------------
1 | using System.IO.Abstractions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Text.RegularExpressions;
7 | using MediaToolkit.Model;
8 | using MediaToolkit.Options;
9 | using MediaToolkit.Properties;
10 | using MediaToolkit.Util;
11 |
12 | namespace MediaToolkit
13 | {
14 | /// -------------------------------------------------------------------------------------------------
15 | /// An engine. This class cannot be inherited.
16 | [Obsolete("Use the new MediaToolkit service.")]
17 | public class Engine : EngineBase
18 | {
19 | ///
20 | /// Event queue for all listeners interested in conversionComplete events.
21 | ///
22 | public event EventHandler ConversionCompleteEvent;
23 |
24 | public Engine(string ffMpegPath, IFileSystem fileSystem)
25 | : base(ffMpegPath, fileSystem)
26 | {
27 | }
28 |
29 | /// -------------------------------------------------------------------------------------------------
30 | ///
31 | /// ---
32 | /// Converts media with conversion options
33 | ///
34 | /// Input file.
35 | /// Output file.
36 | /// Conversion options.
37 | public void Convert(MediaFile inputFile, MediaFile outputFile, ConversionOptions options)
38 | {
39 | EngineParameters engineParams = new EngineParameters
40 | {
41 | InputFile = inputFile,
42 | OutputFile = outputFile,
43 | ConversionOptions = options,
44 | Task = FFmpegTask.Convert
45 | };
46 |
47 | this.FFmpegEngine(engineParams);
48 | }
49 |
50 | /// -------------------------------------------------------------------------------------------------
51 | ///
52 | /// ---
53 | /// Converts media with default options
54 | ///
55 | /// Input file.
56 | /// Output file.
57 | public void Convert(MediaFile inputFile, MediaFile outputFile)
58 | {
59 | EngineParameters engineParams = new EngineParameters
60 | {
61 | InputFile = inputFile,
62 | OutputFile = outputFile,
63 | Task = FFmpegTask.Convert
64 | };
65 |
66 | this.FFmpegEngine(engineParams);
67 | }
68 |
69 | /// Event queue for all listeners interested in convertProgress events.
70 | public event EventHandler ConvertProgressEvent;
71 |
72 | public void CustomCommand(string ffmpegCommand)
73 | {
74 | if(ffmpegCommand.IsNullOrWhiteSpace())
75 | throw new ArgumentNullException(nameof(ffmpegCommand));
76 |
77 | EngineParameters engineParameters = new EngineParameters { CustomArguments = ffmpegCommand };
78 |
79 | this.StartFFmpegProcess(engineParameters);
80 | }
81 |
82 | /// -------------------------------------------------------------------------------------------------
83 | ///
84 | /// Retrieve media metadata
85 | ///
86 | /// Retrieves the metadata for the input file.
87 | public void GetMetadata(MediaFile inputFile)
88 | {
89 | EngineParameters engineParams = new EngineParameters
90 | {
91 | InputFile = inputFile,
92 | Task = FFmpegTask.GetMetaData
93 | };
94 |
95 | this.FFmpegEngine(engineParams);
96 | }
97 |
98 | /// Retrieve a thumbnail image from a video file.
99 | /// Video file.
100 | /// Image file.
101 | /// Conversion options.
102 | public void GetThumbnail(MediaFile inputFile, MediaFile outputFile, ConversionOptions options)
103 | {
104 | EngineParameters engineParams = new EngineParameters
105 | {
106 | InputFile = inputFile,
107 | OutputFile = outputFile,
108 | ConversionOptions = options,
109 | Task = FFmpegTask.GetThumbnail
110 | };
111 |
112 | this.FFmpegEngine(engineParams);
113 | }
114 |
115 | #region Private method - Helpers
116 |
117 | private void FFmpegEngine(EngineParameters engineParameters)
118 | {
119 | if(!engineParameters.InputFile.Filename.StartsWith("http://") && !File.Exists(engineParameters.InputFile.Filename))
120 | {
121 | throw new FileNotFoundException(Resources.Exception_Media_Input_File_Not_Found, engineParameters.InputFile.Filename);
122 | }
123 |
124 | try
125 | {
126 | this.Mutex.WaitOne();
127 | this.StartFFmpegProcess(engineParameters);
128 | }
129 | finally
130 | {
131 | this.Mutex.ReleaseMutex();
132 | }
133 | }
134 |
135 | private ProcessStartInfo GenerateStartInfo(EngineParameters engineParameters)
136 | {
137 | string arguments = CommandBuilder.Serialize(engineParameters);
138 |
139 | return this.GenerateStartInfo(arguments);
140 | }
141 |
142 | private ProcessStartInfo GenerateStartInfo(string arguments)
143 | {
144 | //windows case
145 | if(Path.DirectorySeparatorChar == '\\')
146 | {
147 | return new ProcessStartInfo
148 | {
149 | Arguments = "-nostdin -y -loglevel info " + arguments,
150 | FileName = this.FfmpegFilePath,
151 | CreateNoWindow = true,
152 | RedirectStandardInput = false,
153 | RedirectStandardOutput = true,
154 | RedirectStandardError = true,
155 | UseShellExecute = false,
156 | WindowStyle = ProcessWindowStyle.Hidden
157 | };
158 | }
159 | else //linux case: -nostdin options doesn't exist at least in debian ffmpeg
160 | {
161 | return new ProcessStartInfo
162 | {
163 | Arguments = "-y -loglevel info " + arguments,
164 | FileName = this.FfmpegFilePath,
165 | CreateNoWindow = true,
166 | RedirectStandardInput = false,
167 | RedirectStandardOutput = true,
168 | RedirectStandardError = true,
169 | UseShellExecute = false,
170 | WindowStyle = ProcessWindowStyle.Hidden
171 | };
172 | }
173 | }
174 |
175 | #endregion
176 |
177 | /// -------------------------------------------------------------------------------------------------
178 | /// Raises the conversion complete event.
179 | /// Event information to send to registered event handlers.
180 | private void OnConversionComplete(ConversionCompleteEventArgs e)
181 | {
182 | EventHandler handler = this.ConversionCompleteEvent;
183 | if(handler != null)
184 | {
185 | handler(this, e);
186 | }
187 | }
188 |
189 | /// -------------------------------------------------------------------------------------------------
190 | /// Raises the convert progress event.
191 | /// Event information to send to registered event handlers.
192 | private void OnProgressChanged(ConvertProgressEventArgs e)
193 | {
194 | EventHandler handler = this.ConvertProgressEvent;
195 | if(handler != null)
196 | {
197 | handler(this, e);
198 | }
199 | }
200 |
201 | /// -------------------------------------------------------------------------------------------------
202 | /// Starts FFmpeg process.
203 | ///
204 | /// Thrown when the requested operation is
205 | /// invalid.
206 | ///
207 | ///
208 | /// Thrown when an exception error condition
209 | /// occurs.
210 | ///
211 | /// The engine parameters.
212 | private void StartFFmpegProcess(EngineParameters engineParameters)
213 | {
214 | List receivedMessagesLog = new List();
215 | TimeSpan totalMediaDuration = new TimeSpan();
216 |
217 | ProcessStartInfo processStartInfo = engineParameters.HasCustomArguments
218 | ? this.GenerateStartInfo(engineParameters.CustomArguments)
219 | : this.GenerateStartInfo(engineParameters);
220 |
221 | using(this.FFmpegProcess = Process.Start(processStartInfo))
222 | {
223 | Exception caughtException = null;
224 | if(this.FFmpegProcess == null)
225 | {
226 | throw new InvalidOperationException(Resources.Exceptions_FFmpeg_Process_Not_Running);
227 | }
228 |
229 | this.FFmpegProcess.ErrorDataReceived += (sender, received) =>
230 | {
231 | if(received.Data == null)
232 | return;
233 | #if(DebugToConsole)
234 | Console.WriteLine(received.Data);
235 | #endif
236 | try
237 | {
238 | receivedMessagesLog.Insert(0, received.Data);
239 | if(engineParameters.InputFile != null)
240 | {
241 | RegexEngine.TestVideo(received.Data, engineParameters);
242 | RegexEngine.TestAudio(received.Data, engineParameters);
243 |
244 | Match matchDuration = RegexEngine.Index[RegexEngine.Find.Duration].Match(received.Data);
245 | if(matchDuration.Success)
246 | {
247 | if(engineParameters.InputFile.Metadata == null)
248 | {
249 | engineParameters.InputFile.Metadata = new Metadata();
250 | }
251 |
252 | TimeSpan.TryParse(matchDuration.Groups[1].Value, out totalMediaDuration);
253 | engineParameters.InputFile.Metadata.Duration = totalMediaDuration;
254 | }
255 | }
256 |
257 | ConversionCompleteEventArgs convertCompleteEvent;
258 | ConvertProgressEventArgs progressEvent;
259 |
260 | if(RegexEngine.IsProgressData(received.Data, out progressEvent))
261 | {
262 | progressEvent.TotalDuration = totalMediaDuration;
263 | this.OnProgressChanged(progressEvent);
264 | }
265 | else if(RegexEngine.IsConvertCompleteData(received.Data, out convertCompleteEvent))
266 | {
267 | convertCompleteEvent.TotalDuration = totalMediaDuration;
268 | this.OnConversionComplete(convertCompleteEvent);
269 | }
270 | }
271 | catch(Exception ex)
272 | {
273 | // catch the exception and kill the process since we're in a faulted state
274 | caughtException = ex;
275 |
276 | try
277 | {
278 | this.FFmpegProcess.Kill();
279 | }
280 | catch(InvalidOperationException)
281 | {
282 | // swallow exceptions that are thrown when killing the process,
283 | // one possible candidate is the application ending naturally before we get a chance to kill it
284 | }
285 | }
286 | };
287 |
288 | this.FFmpegProcess.BeginErrorReadLine();
289 | this.FFmpegProcess.WaitForExit();
290 |
291 | if((this.FFmpegProcess.ExitCode != 0 && this.FFmpegProcess.ExitCode != 1) || caughtException != null)
292 | {
293 | throw new Exception(
294 | this.FFmpegProcess.ExitCode + ": " + receivedMessagesLog[1] + receivedMessagesLog[0],
295 | caughtException);
296 | }
297 | }
298 | }
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/MediaToolkit.Test/ConvertTest.cs:
--------------------------------------------------------------------------------
1 | using MediaToolkit.Model;
2 | using MediaToolkit.Options;
3 | using System;
4 | using System.Diagnostics;
5 | using System.Globalization;
6 | using System.IO;
7 | using System.IO.Abstractions;
8 | using Xunit;
9 |
10 | namespace MediaToolkit.Test
11 | {
12 | public class ConvertTest
13 | {
14 | private string _inputFilePath = "";
15 | private string _inputUrlPath = "";
16 | private string _outputFilePath = "";
17 | private bool _printToConsoleEnabled;
18 | private readonly string _ffmpegFilePath = @"C:\ffmpeg\ffmpeg.exe";
19 | private readonly IFileSystem _fileSystem = new FileSystem();
20 |
21 |
22 | public ConvertTest()
23 | {
24 | // Raise progress events?
25 | _printToConsoleEnabled = true;
26 |
27 | const string inputFile = @"";
28 | const string outputFile = @"";
29 |
30 | // ReSharper disable once ConditionIsAlwaysTrueOrFalse
31 | if(inputFile != "")
32 | {
33 | _inputFilePath = inputFile;
34 | if(outputFile != "")
35 | _outputFilePath = outputFile;
36 |
37 | return;
38 | }
39 |
40 | var testAssetsPath = Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\TestVideo");
41 | var testAssetsFullPath = Path.GetFullPath(testAssetsPath);
42 |
43 | if(!Directory.Exists(testAssetsFullPath))
44 | throw new InvalidOperationException($"Directory not found: {testAssetsFullPath}");
45 |
46 | _inputFilePath = $@"{testAssetsFullPath}\BigBunny.m4v";
47 | _inputUrlPath = @"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
48 |
49 | // Note: assuming that tests executed from bin folder
50 | var outputDirectory = Path.Combine(Directory.GetCurrentDirectory(), @"TestOutput");
51 | if(!Directory.Exists(outputDirectory))
52 | Directory.CreateDirectory(outputDirectory);
53 |
54 | _outputFilePath = $@"{outputDirectory}\OuputBunny.mp4";
55 |
56 | if(!File.Exists(_inputFilePath))
57 | throw new InvalidOperationException($@"Test file not found: {_inputFilePath}");
58 | }
59 |
60 | [Fact]
61 | public void Can_CutVideo()
62 | {
63 | string filePath = @"{0}\Cut_Video_Test.mp4";
64 | string outputPath = string.Format(filePath, Path.GetDirectoryName(_outputFilePath));
65 |
66 | var inputFile = new MediaFile { Filename = _inputFilePath };
67 | var outputFile = new MediaFile { Filename = outputPath };
68 |
69 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
70 | {
71 | engine.ConvertProgressEvent += engine_ConvertProgressEvent;
72 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
73 |
74 | engine.GetMetadata(inputFile);
75 |
76 | ConversionOptions options = new ConversionOptions();
77 | options.CutMedia(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(25));
78 |
79 | engine.Convert(inputFile, outputFile, options);
80 | engine.GetMetadata(outputFile);
81 | }
82 |
83 | Assert.True(File.Exists(outputPath));
84 | // Input file is 33 seconds long, seeking to the 30th second and then
85 | // attempting to cut another 25 seconds isn't possible as there's only 3 seconds
86 | // of content length, so instead the library cuts the maximumum possible.
87 | }
88 |
89 | [Fact]
90 | public void Can_CropVideo()
91 | {
92 | string outputPath = string.Format(@"{0}\Crop_Video_Test.mp4", Path.GetDirectoryName(_outputFilePath));
93 |
94 | var inputFile = new MediaFile { Filename = _inputFilePath };
95 | var outputFile = new MediaFile { Filename = outputPath };
96 |
97 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
98 | {
99 | engine.ConvertProgressEvent += engine_ConvertProgressEvent;
100 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
101 |
102 | engine.GetMetadata(inputFile);
103 |
104 | var options = new ConversionOptions();
105 | options.SourceCrop = new CropRectangle()
106 | {
107 | X = 100,
108 | Y = 100,
109 | Width = 50,
110 | Height = 50
111 | };
112 |
113 | engine.Convert(inputFile, outputFile, options);
114 | }
115 | }
116 |
117 | [Fact]
118 | public void Can_GetThumbnail()
119 | {
120 | string outputPath = string.Format(@"{0}\Get_Thumbnail_Test.jpg", Path.GetDirectoryName(_outputFilePath));
121 | if(File.Exists(outputPath))
122 | {
123 | File.Delete(outputPath);
124 | }
125 |
126 | var inputFile = new MediaFile { Filename = _inputFilePath };
127 | var outputFile = new MediaFile { Filename = outputPath };
128 |
129 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
130 | {
131 | engine.ConvertProgressEvent += engine_ConvertProgressEvent;
132 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
133 |
134 | engine.GetMetadata(inputFile);
135 |
136 | var options = new ConversionOptions
137 | {
138 | Seek = TimeSpan.FromSeconds(inputFile.Metadata.Duration.TotalSeconds / 2)
139 | };
140 | engine.GetThumbnail(inputFile, outputFile, options);
141 | }
142 | Assert.True(File.Exists(outputPath));
143 | }
144 |
145 | [Fact]
146 | public void Can_GetThumbnailFromHTTPLink()
147 | {
148 | string outputPath = string.Format(@"{0}\Get_Thumbnail_FromHTTP_Test.jpg", Path.GetDirectoryName(_outputFilePath));
149 |
150 | var inputFile = new MediaFile { Filename = _inputUrlPath };
151 | var outputFile = new MediaFile { Filename = outputPath };
152 |
153 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
154 | {
155 | engine.ConvertProgressEvent += engine_ConvertProgressEvent;
156 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
157 |
158 | engine.GetMetadata(inputFile);
159 |
160 | var options = new ConversionOptions
161 | {
162 | Seek = TimeSpan.FromSeconds(inputFile.Metadata.Duration.TotalSeconds / 2)
163 | };
164 | engine.GetThumbnail(inputFile, outputFile, options);
165 | }
166 | }
167 |
168 | [Fact]
169 | public void Can_GetMetadata()
170 | {
171 | var inputFile = new MediaFile { Filename = _inputFilePath };
172 |
173 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
174 | engine.GetMetadata(inputFile);
175 |
176 | Metadata inputMeta = inputFile.Metadata;
177 |
178 | Debug.Assert(inputMeta.Duration != TimeSpan.Zero, "Media duration is zero", " Likely due to Regex code");
179 | Debug.Assert(inputMeta.VideoData.Format != null, "Video format not found", " Likely due to Regex code");
180 | Debug.Assert(inputMeta.VideoData.ColorModel != null, "Color model not found", " Likely due to Regex code");
181 | Debug.Assert(inputMeta.VideoData.FrameSize != null, "Frame size not found", " Likely due to Regex code");
182 | Debug.Assert(inputMeta.VideoData.Fps.ToString(CultureInfo.InvariantCulture) != "0", "Fps not found",
183 | " Likely due to Regex code");
184 | Debug.Assert(inputMeta.VideoData.BitRateKbs != 0, "Video bitrate not found", " Likely due to Regex code");
185 | Debug.Assert(inputMeta.AudioData.Format != null, "Audio format not found", " Likely due to Regex code");
186 | Debug.Assert(inputMeta.AudioData.SampleRate != null, "Sample rate not found", " Likely due to Regex code");
187 | Debug.Assert(inputMeta.AudioData.ChannelOutput != null, "Channel output not found",
188 | "Likely due to Regex code");
189 | // Audio bit rate for some reson isn't returned by FFmreg for WEBM videos.
190 | //Debug.Assert(inputMeta.AudioData.BitRateKbs != 0, "Audio bitrate not found", " Likely due to Regex code");
191 |
192 | PrintMetadata(inputMeta);
193 | }
194 |
195 | [Fact]
196 | public void Can_ConvertBasic()
197 | {
198 | string outputPath = string.Format(@"{0}\Convert_Basic_Test.avi", Path.GetDirectoryName(_outputFilePath));
199 |
200 | var inputFile = new MediaFile { Filename = _inputFilePath };
201 | var outputFile = new MediaFile { Filename = outputPath };
202 |
203 |
204 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
205 | {
206 | engine.ConvertProgressEvent += engine_ConvertProgressEvent;
207 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
208 |
209 | engine.Convert(inputFile, outputFile);
210 | engine.GetMetadata(inputFile);
211 | engine.GetMetadata(outputFile);
212 | }
213 |
214 | Metadata inputMeta = inputFile.Metadata;
215 | Metadata outputMeta = outputFile.Metadata;
216 |
217 | PrintMetadata(inputMeta);
218 | PrintMetadata(outputMeta);
219 | }
220 |
221 | [Fact]
222 | public void Can_ConvertToGif()
223 | {
224 | string outputPath = string.Format(@"{0}\Convert_GIF_Test.gif", Path.GetDirectoryName(_outputFilePath));
225 |
226 | var inputFile = new MediaFile { Filename = _inputFilePath };
227 | var outputFile = new MediaFile { Filename = outputPath };
228 |
229 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
230 | {
231 | engine.ConvertProgressEvent += engine_ConvertProgressEvent;
232 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
233 |
234 | engine.Convert(inputFile, outputFile);
235 | engine.GetMetadata(inputFile);
236 | engine.GetMetadata(outputFile);
237 | }
238 |
239 | Metadata inputMeta = inputFile.Metadata;
240 | Metadata outputMeta = outputFile.Metadata;
241 |
242 | PrintMetadata(inputMeta);
243 | PrintMetadata(outputMeta);
244 | }
245 |
246 |
247 | [Fact]
248 | public void Can_ConvertToDVD()
249 | {
250 | string outputPath = string.Format("{0}/Convert_DVD_Test.vob", Path.GetDirectoryName(_outputFilePath));
251 |
252 | var inputFile = new MediaFile { Filename = _inputFilePath };
253 | var outputFile = new MediaFile { Filename = outputPath };
254 |
255 | var conversionOptions = new ConversionOptions { Target = Target.DVD, TargetStandard = TargetStandard.PAL };
256 |
257 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
258 | {
259 | engine.ConvertProgressEvent += engine_ConvertProgressEvent;
260 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
261 |
262 | engine.Convert(inputFile, outputFile, conversionOptions);
263 |
264 | engine.GetMetadata(inputFile);
265 | engine.GetMetadata(outputFile);
266 | }
267 |
268 | PrintMetadata(inputFile.Metadata);
269 | PrintMetadata(outputFile.Metadata);
270 | }
271 |
272 | [Fact]
273 | public void Can_TranscodeUsingConversionOptions()
274 | {
275 | string outputPath = string.Format("{0}/Transcode_Test.avi", Path.GetDirectoryName(_outputFilePath));
276 |
277 | var inputFile = new MediaFile { Filename = _inputFilePath };
278 | var outputFile = new MediaFile { Filename = outputPath };
279 | var conversionOptions = new ConversionOptions
280 | {
281 | MaxVideoDuration = TimeSpan.FromSeconds(30),
282 | VideoAspectRatio = VideoAspectRatio.R16_9,
283 | VideoSize = VideoSize.Hd720,
284 | AudioSampleRate = AudioSampleRate.Hz44100
285 | };
286 |
287 |
288 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
289 | {
290 | engine.Convert(inputFile, outputFile, conversionOptions);
291 | }
292 | }
293 |
294 | [Fact]
295 | public void Can_ScaleDownPreservingAspectRatio()
296 | {
297 | string outputPath = string.Format(@"{0}\Convert_Basic_Test.mp4", Path.GetDirectoryName(_outputFilePath));
298 |
299 | var inputFile = new MediaFile { Filename = _inputFilePath };
300 | var outputFile = new MediaFile { Filename = outputPath };
301 |
302 | using(var engine = new Engine(this._ffmpegFilePath, this._fileSystem))
303 | {
304 | engine.ConvertProgressEvent += engine_ConvertProgressEvent;
305 | engine.ConversionCompleteEvent += engine_ConversionCompleteEvent;
306 |
307 | engine.Convert(inputFile, outputFile, new ConversionOptions { VideoSize = VideoSize.Custom, CustomHeight = 120 });
308 | engine.GetMetadata(inputFile);
309 | engine.GetMetadata(outputFile);
310 | }
311 |
312 | Assert.Equal("214x120", outputFile.Metadata.VideoData.FrameSize);
313 |
314 | PrintMetadata(inputFile.Metadata);
315 | PrintMetadata(outputFile.Metadata);
316 | }
317 |
318 | private void engine_ConvertProgressEvent(object sender, ConvertProgressEventArgs e)
319 | {
320 | if(!_printToConsoleEnabled)
321 | return;
322 |
323 | Console.WriteLine("Bitrate: {0}", e.Bitrate);
324 | Console.WriteLine("Fps: {0}", e.Fps);
325 | Console.WriteLine("Frame: {0}", e.Frame);
326 | Console.WriteLine("ProcessedDuration: {0}", e.ProcessedDuration);
327 | Console.WriteLine("SizeKb: {0}", e.SizeKb);
328 | Console.WriteLine("TotalDuration: {0}\n", e.TotalDuration);
329 | }
330 |
331 | private void engine_ConversionCompleteEvent(object sender, ConversionCompleteEventArgs e)
332 | {
333 | if(!_printToConsoleEnabled)
334 | return;
335 |
336 | Console.WriteLine("\n------------\nConversion complete!\n------------");
337 | Console.WriteLine("Bitrate: {0}", e.Bitrate);
338 | Console.WriteLine("Fps: {0}", e.Fps);
339 | Console.WriteLine("Frame: {0}", e.Frame);
340 | Console.WriteLine("ProcessedDuration: {0}", e.ProcessedDuration);
341 | Console.WriteLine("SizeKb: {0}", e.SizeKb);
342 | Console.WriteLine("TotalDuration: {0}\n", e.TotalDuration);
343 | }
344 |
345 | private void PrintMetadata(Metadata meta)
346 | {
347 | if(!_printToConsoleEnabled)
348 | return;
349 |
350 | Console.WriteLine("\n------------\nMetadata\n------------");
351 | Console.WriteLine("Duration: {0}", meta.Duration);
352 | if(meta.VideoData != null)
353 | {
354 | Console.WriteLine("VideoData.Format: {0}", meta.VideoData.Format);
355 | Console.WriteLine("VideoData.ColorModel: {0}", meta.VideoData.ColorModel);
356 | Console.WriteLine("VideoData.FrameSize: {0}", meta.VideoData.FrameSize);
357 | Console.WriteLine("VideoData.Fps: {0}", meta.VideoData.Fps);
358 | Console.WriteLine("VideoData.BitRate: {0}", meta.VideoData.BitRateKbs);
359 | }
360 | else if(meta.AudioData != null)
361 | {
362 | Console.WriteLine("AudioData.Format: {0}", meta.AudioData.Format ?? "");
363 | Console.WriteLine("AudioData.SampleRate: {0}", meta.AudioData.SampleRate ?? "");
364 | Console.WriteLine("AudioData.ChannelOutput: {0}", meta.AudioData.ChannelOutput ?? "");
365 | Console.WriteLine("AudioData.BitRate: {0}\n", (int?)meta.AudioData.BitRateKbs);
366 | }
367 |
368 | }
369 | }
370 | }
371 |
--------------------------------------------------------------------------------