diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index f0792ef..18a8c7d 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -239,9 +239,8 @@ public void Builder_BuildString_Loop() [TestMethod] public void Builder_BuildString_Seek() { - var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))) - .OutputToFile("output.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).Arguments; - Assert.AreEqual("-ss 00:00:10 -i \"input.mp4\" -ss 00:00:10 \"output.mp4\"", str); + var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).OutputToFile("output.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).Arguments; + Assert.AreEqual("-ss 00:00:10.000 -i \"input.mp4\" -ss 00:00:10.000 \"output.mp4\"", str); } [TestMethod] diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index 5ac83a1..a056fd9 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -25,6 +25,22 @@ public async Task Audio_FromStream_Duration() Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration); } + [TestMethod] + public void MediaAnalysis_ParseDuration() + { + var durationHHMMSS = new FFProbeStream { Duration = "05:12:59.177" }; + var longDuration = new FFProbeStream { Duration = "149:07:50.911750" }; + var shortDuration = new FFProbeStream { Duration = "00:00:00.83" }; + + var testdurationHHMMSS = MediaAnalysis.ParseDuration(durationHHMMSS); + var testlongDuration = MediaAnalysis.ParseDuration(longDuration); + var testshortDuration = MediaAnalysis.ParseDuration(shortDuration); + + Assert.IsTrue(testdurationHHMMSS.Days == 0 && testdurationHHMMSS.Hours == 5 && testdurationHHMMSS.Minutes == 12 && testdurationHHMMSS.Seconds == 59 && testdurationHHMMSS.Milliseconds == 177); + Assert.IsTrue(testlongDuration.Days == 6 && testlongDuration.Hours == 5 && testlongDuration.Minutes == 7 && testlongDuration.Seconds == 50 && testlongDuration.Milliseconds == 911); + Assert.IsTrue(testdurationHHMMSS.Days == 0 && testshortDuration.Hours == 0 && testshortDuration.Minutes == 0 && testshortDuration.Seconds == 0 && testshortDuration.Milliseconds == 830); + } + [TestMethod] public async Task Uri_Duration() { diff --git a/FFMpegCore/Assembly.cs b/FFMpegCore/Assembly.cs new file mode 100644 index 0000000..0117671 --- /dev/null +++ b/FFMpegCore/Assembly.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("FFMpegCore.Test")] \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/SeekArgument.cs b/FFMpegCore/FFMpeg/Arguments/SeekArgument.cs index 1057b88..1b58890 100644 --- a/FFMpegCore/FFMpeg/Arguments/SeekArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/SeekArgument.cs @@ -8,11 +8,28 @@ namespace FFMpegCore.Arguments public class SeekArgument : IArgument { public readonly TimeSpan? SeekTo; + public SeekArgument(TimeSpan? seekTo) { SeekTo = seekTo; } - - public string Text => !SeekTo.HasValue ? string.Empty : $"-ss {SeekTo.Value}"; + + public string Text { + get { + if(SeekTo.HasValue) + { + int hours = SeekTo.Value.Hours; + if(SeekTo.Value.Days > 0) + { + hours += SeekTo.Value.Days * 24; + } + return $"-ss {hours.ToString("00")}:{SeekTo.Value.Minutes.ToString("00")}:{SeekTo.Value.Seconds.ToString("00")}.{SeekTo.Value.Milliseconds.ToString("000")}"; + } + else + { + return string.Empty; + } + } + } } } diff --git a/FFMpegCore/FFProbe/MediaAnalysis.cs b/FFMpegCore/FFProbe/MediaAnalysis.cs index 1ee7b18..772997a 100644 --- a/FFMpegCore/FFProbe/MediaAnalysis.cs +++ b/FFMpegCore/FFProbe/MediaAnalysis.cs @@ -18,7 +18,7 @@ private MediaFormat ParseFormat(Format analysisFormat) { return new MediaFormat { - Duration = TimeSpan.Parse(analysisFormat.Duration ?? "0"), + Duration = ParseDuration(analysisFormat.Duration), FormatName = analysisFormat.FormatName, FormatLongName = analysisFormat.FormatLongName, StreamCount = analysisFormat.NbStreams, @@ -66,7 +66,6 @@ private VideoStream ParseVideoStream(FFProbeStream stream) }; } - private AudioStream ParseAudioStream(FFProbeStream stream) { return new AudioStream @@ -115,11 +114,41 @@ public static int ParseIntInvariant(string line) => int.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture); + public static TimeSpan ParseDuration(string duration) + { + if (!string.IsNullOrEmpty(duration)) + { + var match = DurationRegex.Match(duration); + if (match.Success) + { + // ffmpeg may provide < 3-digit number of milliseconds (omitting trailing zeros), which won't simply parse correctly + // e.g. 00:12:02.11 -> 12 minutes 2 seconds and 110 milliseconds + var millisecondsPart = match.Groups[4].Value; + if (millisecondsPart.Length < 3) + { + millisecondsPart = millisecondsPart.PadRight(3, '0'); + } + + var hours = int.Parse(match.Groups[1].Value); + var minutes = int.Parse(match.Groups[2].Value); + var seconds = int.Parse(match.Groups[3].Value); + var milliseconds = int.Parse(millisecondsPart); + return new TimeSpan(0, hours, minutes, seconds, milliseconds); + } + else + { + return TimeSpan.Zero; + } + } + else + { + return TimeSpan.Zero; + } + } + public static TimeSpan ParseDuration(FFProbeStream ffProbeStream) { - return !string.IsNullOrEmpty(ffProbeStream.Duration) - ? TimeSpan.Parse(ffProbeStream.Duration) - : TimeSpan.Parse(TrimTimeSpan(ffProbeStream.GetDuration()) ?? "0"); + return ParseDuration(ffProbeStream.Duration); } private static string? TrimTimeSpan(string? durationTag)