mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
commit
1c016fed9a
10 changed files with 77 additions and 22 deletions
|
@ -7,6 +7,7 @@
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FFMpegCore.Arguments;
|
using FFMpegCore.Arguments;
|
||||||
using FFMpegCore.Exceptions;
|
using FFMpegCore.Exceptions;
|
||||||
|
@ -550,6 +551,27 @@ public void Video_UpdatesProgress()
|
||||||
Assert.AreNotEqual(TimeSpan.Zero, timeDone);
|
Assert.AreNotEqual(TimeSpan.Zero, timeDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod, Timeout(10000)]
|
||||||
|
public void Video_OutputsData()
|
||||||
|
{
|
||||||
|
var outputFile = new TemporaryFile("out.mp4");
|
||||||
|
var dataReceived = false;
|
||||||
|
|
||||||
|
FFMpegOptions.Configure(opt => opt.Encoding = Encoding.UTF8);
|
||||||
|
var success = FFMpegArguments
|
||||||
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
|
.WithGlobalOptions(options => options
|
||||||
|
.WithVerbosityLevel(VerbosityLevel.Info))
|
||||||
|
.OutputToFile(outputFile, false, opt => opt
|
||||||
|
.WithDuration(TimeSpan.FromSeconds(2)))
|
||||||
|
.NotifyOnOutput((_, _) => dataReceived = true)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
|
||||||
|
Assert.IsTrue(dataReceived);
|
||||||
|
Assert.IsTrue(success);
|
||||||
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_TranscodeInMemory()
|
public void Video_TranscodeInMemory()
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,7 @@ public class ContainerFormat
|
||||||
public bool DemuxingSupported { get; private set; }
|
public bool DemuxingSupported { get; private set; }
|
||||||
public bool MuxingSupported { get; private set; }
|
public bool MuxingSupported { get; private set; }
|
||||||
public string Description { get; private set; } = null!;
|
public string Description { get; private set; } = null!;
|
||||||
|
|
||||||
public string Extension
|
public string Extension
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -36,8 +37,8 @@ internal static bool TryParse(string line, out ContainerFormat fmt)
|
||||||
|
|
||||||
fmt = new ContainerFormat(match.Groups[3].Value)
|
fmt = new ContainerFormat(match.Groups[3].Value)
|
||||||
{
|
{
|
||||||
DemuxingSupported = match.Groups[1].Value == " ",
|
DemuxingSupported = match.Groups[1].Value != " ",
|
||||||
MuxingSupported = match.Groups[2].Value == " ",
|
MuxingSupported = match.Groups[2].Value != " ",
|
||||||
Description = match.Groups[4].Value
|
Description = match.Groups[4].Value
|
||||||
};
|
};
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -17,6 +17,7 @@ public class FFMpegArgumentProcessor
|
||||||
private readonly FFMpegArguments _ffMpegArguments;
|
private readonly FFMpegArguments _ffMpegArguments;
|
||||||
private Action<double>? _onPercentageProgress;
|
private Action<double>? _onPercentageProgress;
|
||||||
private Action<TimeSpan>? _onTimeProgress;
|
private Action<TimeSpan>? _onTimeProgress;
|
||||||
|
private Action<string, DataType>? _onOutput;
|
||||||
private TimeSpan? _totalTimespan;
|
private TimeSpan? _totalTimespan;
|
||||||
|
|
||||||
internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments)
|
internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments)
|
||||||
|
@ -39,6 +40,11 @@ public FFMpegArgumentProcessor NotifyOnProgress(Action<TimeSpan> onTimeProgress)
|
||||||
_onTimeProgress = onTimeProgress;
|
_onTimeProgress = onTimeProgress;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public FFMpegArgumentProcessor NotifyOnOutput(Action<string, DataType> onOutput)
|
||||||
|
{
|
||||||
|
_onOutput = onOutput;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
public FFMpegArgumentProcessor CancellableThrough(out Action cancel)
|
public FFMpegArgumentProcessor CancellableThrough(out Action cancel)
|
||||||
{
|
{
|
||||||
cancel = () => CancelEvent?.Invoke(this, EventArgs.Empty);
|
cancel = () => CancelEvent?.Invoke(this, EventArgs.Empty);
|
||||||
|
@ -130,10 +136,17 @@ private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSo
|
||||||
{
|
{
|
||||||
FFMpegHelper.RootExceptionCheck();
|
FFMpegHelper.RootExceptionCheck();
|
||||||
FFMpegHelper.VerifyFFMpegExists();
|
FFMpegHelper.VerifyFFMpegExists();
|
||||||
var instance = new Instance(FFMpegOptions.Options.FFmpegBinary(), _ffMpegArguments.Text);
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = FFMpegOptions.Options.FFmpegBinary(),
|
||||||
|
Arguments = _ffMpegArguments.Text,
|
||||||
|
StandardOutputEncoding = FFMpegOptions.Options.Encoding,
|
||||||
|
StandardErrorEncoding = FFMpegOptions.Options.Encoding,
|
||||||
|
};
|
||||||
|
var instance = new Instance(startInfo);
|
||||||
cancellationTokenSource = new CancellationTokenSource();
|
cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
if (_onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
|
if (_onOutput != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
|
||||||
instance.DataReceived += OutputData;
|
instance.DataReceived += OutputData;
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -150,8 +163,10 @@ private static bool HandleException(bool throwOnError, Exception e, IReadOnlyLis
|
||||||
|
|
||||||
private void OutputData(object sender, (DataType Type, string Data) msg)
|
private void OutputData(object sender, (DataType Type, string Data) msg)
|
||||||
{
|
{
|
||||||
var match = ProgressRegex.Match(msg.Data);
|
|
||||||
Debug.WriteLine(msg.Data);
|
Debug.WriteLine(msg.Data);
|
||||||
|
_onOutput?.Invoke(msg.Data, msg.Type);
|
||||||
|
|
||||||
|
var match = ProgressRegex.Match(msg.Data);
|
||||||
if (!match.Success) return;
|
if (!match.Success) return;
|
||||||
|
|
||||||
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace FFMpegCore
|
namespace FFMpegCore
|
||||||
|
@ -40,6 +41,8 @@ static FFMpegOptions()
|
||||||
|
|
||||||
public string RootDirectory { get; set; } = DefaultRoot;
|
public string RootDirectory { get; set; } = DefaultRoot;
|
||||||
public string TempDirectory { get; set; } = DefaultTemp;
|
public string TempDirectory { get; set; } = DefaultTemp;
|
||||||
|
public bool UseCache { get; set; } = true;
|
||||||
|
public Encoding Encoding { get; set; } = Encoding.Default;
|
||||||
|
|
||||||
public string FFmpegBinary() => FFBinary("FFMpeg");
|
public string FFmpegBinary() => FFBinary("FFMpeg");
|
||||||
|
|
||||||
|
@ -47,8 +50,6 @@ static FFMpegOptions()
|
||||||
|
|
||||||
public Dictionary<string, string> ExtensionOverrides { get; private set; } = new Dictionary<string, string>();
|
public Dictionary<string, string> ExtensionOverrides { get; private set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
public bool UseCache { get; set; } = true;
|
|
||||||
|
|
||||||
private static string FFBinary(string name)
|
private static string FFBinary(string name)
|
||||||
{
|
{
|
||||||
var ffName = name.ToLowerInvariant();
|
var ffName = name.ToLowerInvariant();
|
||||||
|
|
|
@ -9,9 +9,10 @@
|
||||||
<Version>3.0.0.0</Version>
|
<Version>3.0.0.0</Version>
|
||||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
||||||
<FileVersion>3.0.0.0</FileVersion>
|
<FileVersion>3.0.0.0</FileVersion>
|
||||||
<PackageReleaseNotes>- Also include ffmpeg output data on non-zero exit code</PackageReleaseNotes>
|
<PackageReleaseNotes>- return null from FFProbe.Analyse* when no media format was detected
|
||||||
|
- Expose tags as string dictionary on IMediaAnalysis (thanks hey-red)</PackageReleaseNotes>
|
||||||
<LangVersion>8</LangVersion>
|
<LangVersion>8</LangVersion>
|
||||||
<PackageVersion>3.2.4</PackageVersion>
|
<PackageVersion>3.4.0</PackageVersion>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<Authors>Malte Rosenbjerg, Vlad Jerca</Authors>
|
<Authors>Malte Rosenbjerg, Vlad Jerca</Authors>
|
||||||
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags>
|
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags>
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Instances" Version="1.6.0" />
|
<PackageReference Include="Instances" Version="1.6.0" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="5.0.0" />
|
<PackageReference Include="System.Drawing.Common" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="5.0.0" />
|
<PackageReference Include="System.Text.Json" Version="5.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace FFMpegCore
|
||||||
{
|
{
|
||||||
public static class FFProbe
|
public static class FFProbe
|
||||||
{
|
{
|
||||||
public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.MaxValue)
|
public static IMediaAnalysis? Analyse(string filePath, int outputCapacity = int.MaxValue)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
||||||
|
@ -21,13 +21,13 @@ public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.M
|
||||||
instance.BlockUntilFinished();
|
instance.BlockUntilFinished();
|
||||||
return ParseOutput(filePath, instance);
|
return ParseOutput(filePath, instance);
|
||||||
}
|
}
|
||||||
public static IMediaAnalysis Analyse(Uri uri, int outputCapacity = int.MaxValue)
|
public static IMediaAnalysis? Analyse(Uri uri, int outputCapacity = int.MaxValue)
|
||||||
{
|
{
|
||||||
using var instance = PrepareInstance(uri.AbsoluteUri, outputCapacity);
|
using var instance = PrepareInstance(uri.AbsoluteUri, outputCapacity);
|
||||||
instance.BlockUntilFinished();
|
instance.BlockUntilFinished();
|
||||||
return ParseOutput(uri.AbsoluteUri, instance);
|
return ParseOutput(uri.AbsoluteUri, instance);
|
||||||
}
|
}
|
||||||
public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.MaxValue)
|
public static IMediaAnalysis? Analyse(Stream stream, int outputCapacity = int.MaxValue)
|
||||||
{
|
{
|
||||||
var streamPipeSource = new StreamPipeSource(stream);
|
var streamPipeSource = new StreamPipeSource(stream);
|
||||||
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
||||||
|
@ -50,7 +50,7 @@ public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.Max
|
||||||
|
|
||||||
return ParseOutput(pipeArgument.PipePath, instance);
|
return ParseOutput(pipeArgument.PipePath, instance);
|
||||||
}
|
}
|
||||||
public static async Task<IMediaAnalysis> AnalyseAsync(string filePath, int outputCapacity = int.MaxValue)
|
public static async Task<IMediaAnalysis?> AnalyseAsync(string filePath, int outputCapacity = int.MaxValue)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
||||||
|
@ -59,13 +59,13 @@ public static async Task<IMediaAnalysis> AnalyseAsync(string filePath, int outpu
|
||||||
await instance.FinishedRunning();
|
await instance.FinishedRunning();
|
||||||
return ParseOutput(filePath, instance);
|
return ParseOutput(filePath, instance);
|
||||||
}
|
}
|
||||||
public static async Task<IMediaAnalysis> AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue)
|
public static async Task<IMediaAnalysis?> AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue)
|
||||||
{
|
{
|
||||||
using var instance = PrepareInstance(uri.AbsoluteUri, outputCapacity);
|
using var instance = PrepareInstance(uri.AbsoluteUri, outputCapacity);
|
||||||
await instance.FinishedRunning();
|
await instance.FinishedRunning();
|
||||||
return ParseOutput(uri.AbsoluteUri, instance);
|
return ParseOutput(uri.AbsoluteUri, instance);
|
||||||
}
|
}
|
||||||
public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, int outputCapacity = int.MaxValue)
|
public static async Task<IMediaAnalysis?> AnalyseAsync(Stream stream, int outputCapacity = int.MaxValue)
|
||||||
{
|
{
|
||||||
var streamPipeSource = new StreamPipeSource(stream);
|
var streamPipeSource = new StreamPipeSource(stream);
|
||||||
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
||||||
|
@ -92,13 +92,14 @@ public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, int outputC
|
||||||
return ParseOutput(pipeArgument.PipePath, instance);
|
return ParseOutput(pipeArgument.PipePath, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IMediaAnalysis ParseOutput(string filePath, Instance instance)
|
private static IMediaAnalysis? ParseOutput(string filePath, Instance instance)
|
||||||
{
|
{
|
||||||
var json = string.Join(string.Empty, instance.OutputData);
|
var json = string.Join(string.Empty, instance.OutputData);
|
||||||
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeAnalysis>(json, new JsonSerializerOptions
|
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeAnalysis>(json, new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true
|
PropertyNameCaseInsensitive = true
|
||||||
})!;
|
})!;
|
||||||
|
if (ffprobeAnalysis?.Format == null) return null;
|
||||||
return new MediaAnalysis(filePath, ffprobeAnalysis);
|
return new MediaAnalysis(filePath, ffprobeAnalysis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace FFMpegCore
|
||||||
internal class MediaAnalysis : IMediaAnalysis
|
internal class MediaAnalysis : IMediaAnalysis
|
||||||
{
|
{
|
||||||
private static readonly Regex DurationRegex = new Regex("^(\\d{1,2}:\\d{1,2}:\\d{1,2}(.\\d{1,7})?)", RegexOptions.Compiled);
|
private static readonly Regex DurationRegex = new Regex("^(\\d{1,2}:\\d{1,2}:\\d{1,2}(.\\d{1,7})?)", RegexOptions.Compiled);
|
||||||
|
|
||||||
internal MediaAnalysis(string path, FFProbeAnalysis analysis)
|
internal MediaAnalysis(string path, FFProbeAnalysis analysis)
|
||||||
{
|
{
|
||||||
Format = ParseFormat(analysis.Format);
|
Format = ParseFormat(analysis.Format);
|
||||||
|
@ -27,14 +28,15 @@ private MediaFormat ParseFormat(Format analysisFormat)
|
||||||
FormatLongName = analysisFormat.FormatLongName,
|
FormatLongName = analysisFormat.FormatLongName,
|
||||||
StreamCount = analysisFormat.NbStreams,
|
StreamCount = analysisFormat.NbStreams,
|
||||||
ProbeScore = analysisFormat.ProbeScore,
|
ProbeScore = analysisFormat.ProbeScore,
|
||||||
BitRate = long.Parse(analysisFormat.BitRate ?? "0")
|
BitRate = long.Parse(analysisFormat.BitRate ?? "0"),
|
||||||
|
Tags = analysisFormat.Tags,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
public string Extension => System.IO.Path.GetExtension(Path);
|
public string Extension => System.IO.Path.GetExtension(Path);
|
||||||
|
|
||||||
public TimeSpan Duration => new []
|
public TimeSpan Duration => new[]
|
||||||
{
|
{
|
||||||
Format.Duration,
|
Format.Duration,
|
||||||
PrimaryVideoStream?.Duration ?? TimeSpan.Zero,
|
PrimaryVideoStream?.Duration ?? TimeSpan.Zero,
|
||||||
|
@ -67,7 +69,8 @@ private VideoStream ParseVideoStream(FFProbeStream stream)
|
||||||
Profile = stream.Profile,
|
Profile = stream.Profile,
|
||||||
PixelFormat = stream.PixelFormat,
|
PixelFormat = stream.PixelFormat,
|
||||||
Rotation = (int)float.Parse(stream.GetRotate() ?? "0"),
|
Rotation = (int)float.Parse(stream.GetRotate() ?? "0"),
|
||||||
Language = stream.GetLanguage()
|
Language = stream.GetLanguage(),
|
||||||
|
Tags = stream.Tags,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +80,7 @@ private static TimeSpan ParseDuration(FFProbeStream ffProbeStream)
|
||||||
? TimeSpan.Parse(ffProbeStream.Duration)
|
? TimeSpan.Parse(ffProbeStream.Duration)
|
||||||
: TimeSpan.Parse(TrimTimeSpan(ffProbeStream.GetDuration()) ?? "0");
|
: TimeSpan.Parse(TrimTimeSpan(ffProbeStream.GetDuration()) ?? "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? TrimTimeSpan(string? durationTag)
|
private static string? TrimTimeSpan(string? durationTag)
|
||||||
{
|
{
|
||||||
var durationMatch = DurationRegex.Match(durationTag ?? "");
|
var durationMatch = DurationRegex.Match(durationTag ?? "");
|
||||||
|
@ -96,17 +100,20 @@ private AudioStream ParseAudioStream(FFProbeStream stream)
|
||||||
Duration = ParseDuration(stream),
|
Duration = ParseDuration(stream),
|
||||||
SampleRateHz = !string.IsNullOrEmpty(stream.SampleRate) ? ParseIntInvariant(stream.SampleRate) : default,
|
SampleRateHz = !string.IsNullOrEmpty(stream.SampleRate) ? ParseIntInvariant(stream.SampleRate) : default,
|
||||||
Profile = stream.Profile,
|
Profile = stream.Profile,
|
||||||
Language = stream.GetLanguage()
|
Language = stream.GetLanguage(),
|
||||||
|
Tags = stream.Tags,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double DivideRatio((double, double) ratio) => ratio.Item1 / ratio.Item2;
|
private static double DivideRatio((double, double) ratio) => ratio.Item1 / ratio.Item2;
|
||||||
|
|
||||||
private static (int, int) ParseRatioInt(string input, char separator)
|
private static (int, int) ParseRatioInt(string input, char separator)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input)) return (0, 0);
|
if (string.IsNullOrEmpty(input)) return (0, 0);
|
||||||
var ratio = input.Split(separator);
|
var ratio = input.Split(separator);
|
||||||
return (ParseIntInvariant(ratio[0]), ParseIntInvariant(ratio[1]));
|
return (ParseIntInvariant(ratio[0]), ParseIntInvariant(ratio[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (double, double) ParseRatioDouble(string input, char separator)
|
private static (double, double) ParseRatioDouble(string input, char separator)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input)) return (0, 0);
|
if (string.IsNullOrEmpty(input)) return (0, 0);
|
||||||
|
@ -116,6 +123,7 @@ private static (double, double) ParseRatioDouble(string input, char separator)
|
||||||
|
|
||||||
private static double ParseDoubleInvariant(string line) =>
|
private static double ParseDoubleInvariant(string line) =>
|
||||||
double.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
|
double.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
private static int ParseIntInvariant(string line) =>
|
private static int ParseIntInvariant(string line) =>
|
||||||
int.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
|
int.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace FFMpegCore
|
namespace FFMpegCore
|
||||||
{
|
{
|
||||||
|
@ -10,5 +11,6 @@ public class MediaFormat
|
||||||
public int StreamCount { get; set; }
|
public int StreamCount { get; set; }
|
||||||
public double ProbeScore { get; set; }
|
public double ProbeScore { get; set; }
|
||||||
public double BitRate { get; set; }
|
public double BitRate { get; set; }
|
||||||
|
public Dictionary<string, string>? Tags { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
using FFMpegCore.Enums;
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace FFMpegCore
|
namespace FFMpegCore
|
||||||
{
|
{
|
||||||
|
@ -11,6 +13,7 @@ public class MediaStream
|
||||||
public int BitRate { get; internal set; }
|
public int BitRate { get; internal set; }
|
||||||
public TimeSpan Duration { get; internal set; }
|
public TimeSpan Duration { get; internal set; }
|
||||||
public string? Language { get; internal set; }
|
public string? Language { get; internal set; }
|
||||||
|
public Dictionary<string, string>? Tags { get; internal set; }
|
||||||
|
|
||||||
public Codec GetCodecInfo() => FFMpeg.GetCodec(CodecName);
|
public Codec GetCodecInfo() => FFMpeg.GetCodec(CodecName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,6 +213,7 @@ The root and temp directory for the ffmpeg binaries can be configured via the `f
|
||||||
<a href="https://github.com/tugrulelmas"><img src="https://avatars3.githubusercontent.com/u/3829187?v=4" title="tugrulelmas" width="80" height="80"></a>
|
<a href="https://github.com/tugrulelmas"><img src="https://avatars3.githubusercontent.com/u/3829187?v=4" title="tugrulelmas" width="80" height="80"></a>
|
||||||
<a href="https://github.com/rosenbjerg"><img src="https://avatars3.githubusercontent.com/u/11181960?v=4" title="rosenbjerg" width="80" height="80"></a>
|
<a href="https://github.com/rosenbjerg"><img src="https://avatars3.githubusercontent.com/u/11181960?v=4" title="rosenbjerg" width="80" height="80"></a>
|
||||||
<a href="https://github.com/WeihanLi"><img src="https://avatars3.githubusercontent.com/u/7604648?v=4" title="weihanli" width="80" height="80"></a>
|
<a href="https://github.com/WeihanLi"><img src="https://avatars3.githubusercontent.com/u/7604648?v=4" title="weihanli" width="80" height="80"></a>
|
||||||
|
<a href="https://github.com/tiesont"><img src="https://avatars3.githubusercontent.com/u/420293?v=4" title="tiesont" width="80" height="80"></a>
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue