Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve FFProbe output parsing and add support for streams with only audio sources #5

Merged
merged 2 commits into from Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions FFMpegCore.Test/FFMpegCore.Test.csproj
Expand Up @@ -30,6 +30,9 @@
<None Update="Resources\audio.mp3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\audio_only.mp4">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\cover.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
1 change: 1 addition & 0 deletions FFMpegCore.Test/Resources/VideoLibrary.cs
Expand Up @@ -17,6 +17,7 @@ public enum ImageType
public static class VideoLibrary
{
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 LocalAudio = new FileInfo(".\\Resources\\audio.mp3");
public static readonly FileInfo LocalCover = new FileInfo(".\\Resources\\cover.png");
Expand Down
Binary file added FFMpegCore.Test/Resources/audio_only.mp4
Binary file not shown.
10 changes: 10 additions & 0 deletions FFMpegCore.Test/VideoTest.cs
Expand Up @@ -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 changes: 41 additions & 0 deletions FFMpegCore/FFMPEG/FFMpegStreamMetadata.cs
@@ -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; }
}
}
86 changes: 36 additions & 50 deletions FFMpegCore/FFMPEG/FFProbe.cs
@@ -1,14 +1,15 @@
using FFMpegCore.Helpers;
using FFMpegCore.FFMPEG.Exceptions;
using FFMpegCore.Helpers;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;

namespace FFMpegCore.FFMPEG
{
public sealed class FFProbe : FFBase
{
static readonly double BITS_TO_MB = 1024 * 1024 * 8;

public FFProbe(): base()
{
FFProbeHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
Expand Down Expand Up @@ -38,71 +39,56 @@ public VideoInfo ParseVideoInfo(VideoInfo info)
var jsonOutput =
RunProcess($"-v quiet -print_format json -show_streams \"{info.FullName}\"");

var metadata = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(jsonOutput);
int videoIndex = metadata["streams"][0]["codec_type"] == "video" ? 0 : 1,
audioIndex = 1 - videoIndex;
var metadata = JsonConvert.DeserializeObject<FFMpegStreamMetadata>(jsonOutput);

var bitRate = Convert.ToDouble(metadata["streams"][videoIndex]["bit_rate"], CultureInfo.InvariantCulture);

try
{
var duration = Convert.ToDouble(metadata["streams"][videoIndex]["duration"], CultureInfo.InvariantCulture);
info.Duration = TimeSpan.FromSeconds(duration);
info.Duration = info.Duration.Subtract(TimeSpan.FromMilliseconds(info.Duration.Milliseconds));
}
catch (Exception)
if (metadata.Streams == null || metadata.Streams.Count == 0)
{
info.Duration = TimeSpan.FromSeconds(0);
throw new FFMpegException(FFMpegExceptionType.File, $"No video or audio streams could be detected. Source: ${info.FullName}");
}

var video = metadata.Streams.Find(s => s.CodecType == "video");
var audio = metadata.Streams.Find(s => s.CodecType == "audio");

double videoSize = 0d;
double audioSize = 0d;

// Get video size in megabytes
double videoSize = 0,
audioSize = 0;
var duration = TimeSpan.FromSeconds(double.TryParse((video ?? audio).Duration, out var output) ? output : 0);
info.Duration = duration.Subtract(TimeSpan.FromMilliseconds(duration.Milliseconds));

try
if (video != null)
{
info.VideoFormat = metadata["streams"][videoIndex]["codec_name"];
videoSize = bitRate * info.Duration / 8388608;
}
catch (Exception)
var bitRate = Convert.ToDouble(video.BitRate, CultureInfo.InvariantCulture);
var fr = video.FrameRate.Split('/');
var commonDenominator = FFProbeHelper.Gcd(video.Width, video.Height);

videoSize = bitRate * duration.TotalSeconds / BITS_TO_MB;

info.VideoFormat = video.CodecName;
info.Width = video.Width;
info.Height = video.Height;
info.FrameRate = Math.Round(
Convert.ToDouble(fr[0], CultureInfo.InvariantCulture) /
Convert.ToDouble(fr[1], CultureInfo.InvariantCulture),
3);
info.Ratio = video.Width / commonDenominator + ":" + video.Height / commonDenominator;
} else
{
info.VideoFormat = "none";
}

// Get audio format - wrap for exceptions if the video has no audio
try
if (audio != null)
{
info.AudioFormat = metadata["streams"][audioIndex]["codec_name"];
audioSize = bitRate * info.Duration / 8388608;
}
catch (Exception)
var bitRate = Convert.ToDouble(audio.BitRate, CultureInfo.InvariantCulture);
info.AudioFormat = audio.CodecName;
audioSize = bitRate * duration.TotalSeconds / BITS_TO_MB;
} else
{
info.AudioFormat = "none";
}

// Get video format


// 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(
Convert.ToDouble(fr[0], CultureInfo.InvariantCulture) /
Convert.ToDouble(fr[1], CultureInfo.InvariantCulture),
3);

return info;
}

Expand Down