mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
Merge pull request #5 from vladjerca/fix/improve_metadata_parse
Improve FFProbe output parsing and add support for streams with only audio sources
Former-commit-id: 134afd6e06
This commit is contained in:
commit
076772a037
6 changed files with 92 additions and 51 deletions
|
@ -30,6 +30,9 @@
|
||||||
<None Update="Resources\audio.mp3">
|
<None Update="Resources\audio.mp3">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Resources\audio_only.mp4">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="Resources\cover.png">
|
<None Update="Resources\cover.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|
|
@ -17,6 +17,7 @@ public enum ImageType
|
||||||
public static class VideoLibrary
|
public static class VideoLibrary
|
||||||
{
|
{
|
||||||
public static readonly FileInfo LocalVideo = new FileInfo(".\\Resources\\input.mp4");
|
public static readonly FileInfo LocalVideo = new FileInfo(".\\Resources\\input.mp4");
|
||||||
|
public static readonly FileInfo LocalVideoAudioOnly = new FileInfo(".\\Resources\\audio_only.mp4");
|
||||||
public static readonly FileInfo LocalVideoNoAudio = new FileInfo(".\\Resources\\mute.mp4");
|
public static readonly FileInfo LocalVideoNoAudio = new FileInfo(".\\Resources\\mute.mp4");
|
||||||
public static readonly FileInfo LocalAudio = new FileInfo(".\\Resources\\audio.mp3");
|
public static readonly FileInfo LocalAudio = new FileInfo(".\\Resources\\audio.mp3");
|
||||||
public static readonly FileInfo LocalCover = new FileInfo(".\\Resources\\cover.png");
|
public static readonly FileInfo LocalCover = new FileInfo(".\\Resources\\cover.png");
|
||||||
|
|
BIN
FFMpegCore.Test/Resources/audio_only.mp4
Normal file
BIN
FFMpegCore.Test/Resources/audio_only.mp4
Normal file
Binary file not shown.
|
@ -308,5 +308,15 @@ public void Video_Join_Image_Sequence()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Video_With_Only_Audio_Should_Extract_Metadata()
|
||||||
|
{
|
||||||
|
var video = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoAudioOnly);
|
||||||
|
Assert.AreEqual(video.VideoFormat, "none");
|
||||||
|
Assert.AreEqual(video.AudioFormat, "aac");
|
||||||
|
Assert.AreEqual(video.Duration.TotalSeconds, 79);
|
||||||
|
Assert.AreEqual(video.Size, 1.25);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
FFMpegCore/FFMPEG/FFMpegStreamMetadata.cs
Normal file
41
FFMpegCore/FFMPEG/FFMpegStreamMetadata.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FFMpegCore.FFMPEG
|
||||||
|
{
|
||||||
|
internal class Stream
|
||||||
|
{
|
||||||
|
[JsonProperty("index")]
|
||||||
|
internal int Index { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("codec_name")]
|
||||||
|
internal string CodecName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("bit_rate")]
|
||||||
|
internal string BitRate { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("profile")]
|
||||||
|
internal string Profile { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("codec_type")]
|
||||||
|
internal string CodecType { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("width")]
|
||||||
|
internal int Width { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("height")]
|
||||||
|
internal int Height { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("duration")]
|
||||||
|
internal string Duration { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("r_frame_rate")]
|
||||||
|
internal string FrameRate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class FFMpegStreamMetadata
|
||||||
|
{
|
||||||
|
[JsonProperty("streams")]
|
||||||
|
internal List<Stream> Streams { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,15 @@
|
||||||
using FFMpegCore.Helpers;
|
using FFMpegCore.FFMPEG.Exceptions;
|
||||||
|
using FFMpegCore.Helpers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG
|
namespace FFMpegCore.FFMPEG
|
||||||
{
|
{
|
||||||
public sealed class FFProbe : FFBase
|
public sealed class FFProbe : FFBase
|
||||||
{
|
{
|
||||||
|
static readonly double BITS_TO_MB = 1024 * 1024 * 8;
|
||||||
|
|
||||||
public FFProbe(): base()
|
public FFProbe(): base()
|
||||||
{
|
{
|
||||||
FFProbeHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
FFProbeHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
||||||
|
@ -38,70 +39,55 @@ public VideoInfo ParseVideoInfo(VideoInfo info)
|
||||||
var jsonOutput =
|
var jsonOutput =
|
||||||
RunProcess($"-v quiet -print_format json -show_streams \"{info.FullName}\"");
|
RunProcess($"-v quiet -print_format json -show_streams \"{info.FullName}\"");
|
||||||
|
|
||||||
var metadata = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(jsonOutput);
|
var metadata = JsonConvert.DeserializeObject<FFMpegStreamMetadata>(jsonOutput);
|
||||||
int videoIndex = metadata["streams"][0]["codec_type"] == "video" ? 0 : 1,
|
|
||||||
audioIndex = 1 - videoIndex;
|
|
||||||
|
|
||||||
var bitRate = Convert.ToDouble(metadata["streams"][videoIndex]["bit_rate"], CultureInfo.InvariantCulture);
|
if (metadata.Streams == null || metadata.Streams.Count == 0)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var duration = Convert.ToDouble(metadata["streams"][videoIndex]["duration"], CultureInfo.InvariantCulture);
|
throw new FFMpegException(FFMpegExceptionType.File, $"No video or audio streams could be detected. Source: ${info.FullName}");
|
||||||
info.Duration = TimeSpan.FromSeconds(duration);
|
|
||||||
info.Duration = info.Duration.Subtract(TimeSpan.FromMilliseconds(info.Duration.Milliseconds));
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
info.Duration = TimeSpan.FromSeconds(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var video = metadata.Streams.Find(s => s.CodecType == "video");
|
||||||
|
var audio = metadata.Streams.Find(s => s.CodecType == "audio");
|
||||||
|
|
||||||
// Get video size in megabytes
|
double videoSize = 0d;
|
||||||
double videoSize = 0,
|
double audioSize = 0d;
|
||||||
audioSize = 0;
|
|
||||||
|
|
||||||
try
|
var duration = TimeSpan.FromSeconds(double.TryParse((video ?? audio).Duration, out var output) ? output : 0);
|
||||||
|
info.Duration = duration.Subtract(TimeSpan.FromMilliseconds(duration.Milliseconds));
|
||||||
|
|
||||||
|
if (video != null)
|
||||||
{
|
{
|
||||||
info.VideoFormat = metadata["streams"][videoIndex]["codec_name"];
|
var bitRate = Convert.ToDouble(video.BitRate, CultureInfo.InvariantCulture);
|
||||||
videoSize = bitRate * info.Duration / 8388608;
|
var fr = video.FrameRate.Split('/');
|
||||||
}
|
var commonDenominator = FFProbeHelper.Gcd(video.Width, video.Height);
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
info.VideoFormat = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get audio format - wrap for exceptions if the video has no audio
|
videoSize = bitRate * duration.TotalSeconds / BITS_TO_MB;
|
||||||
try
|
|
||||||
{
|
|
||||||
info.AudioFormat = metadata["streams"][audioIndex]["codec_name"];
|
|
||||||
audioSize = bitRate * info.Duration / 8388608;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
info.AudioFormat = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get video format
|
info.VideoFormat = video.CodecName;
|
||||||
|
info.Width = video.Width;
|
||||||
|
info.Height = video.Height;
|
||||||
// Get video width
|
|
||||||
info.Width = metadata["streams"][videoIndex]["width"];
|
|
||||||
|
|
||||||
// Get video height
|
|
||||||
info.Height = metadata["streams"][videoIndex]["height"];
|
|
||||||
|
|
||||||
info.Size = Math.Round(videoSize + audioSize, 2);
|
|
||||||
|
|
||||||
// Get video aspect ratio
|
|
||||||
var cd = FFProbeHelper.Gcd(info.Width, info.Height);
|
|
||||||
info.Ratio = info.Width / cd + ":" + info.Height / cd;
|
|
||||||
|
|
||||||
// Get video framerate
|
|
||||||
var fr = ((string)metadata["streams"][videoIndex]["r_frame_rate"]).Split('/');
|
|
||||||
info.FrameRate = Math.Round(
|
info.FrameRate = Math.Round(
|
||||||
Convert.ToDouble(fr[0], CultureInfo.InvariantCulture) /
|
Convert.ToDouble(fr[0], CultureInfo.InvariantCulture) /
|
||||||
Convert.ToDouble(fr[1], CultureInfo.InvariantCulture),
|
Convert.ToDouble(fr[1], CultureInfo.InvariantCulture),
|
||||||
3);
|
3);
|
||||||
|
info.Ratio = video.Width / commonDenominator + ":" + video.Height / commonDenominator;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
info.VideoFormat = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio != null)
|
||||||
|
{
|
||||||
|
var bitRate = Convert.ToDouble(audio.BitRate, CultureInfo.InvariantCulture);
|
||||||
|
info.AudioFormat = audio.CodecName;
|
||||||
|
audioSize = bitRate * duration.TotalSeconds / BITS_TO_MB;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
info.AudioFormat = "none";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Size = Math.Round(videoSize + audioSize, 2);
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue