diff --git a/CHANGELOG.md b/CHANGELOG.md index 772bd56ba..882c855e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [SIL.Archiving] Added public property isValid to IMDIPackage. - [SIL.Archiving] Added public event InitializationFailed to IMDIArchivingDlgViewModel. - [SIL.Archiving] Added the following properties to ArchivingDlgViewModel as an alternative way to customize the initial summary displayed: GetOverriddenPreArchivingMessages, InitialFileGroupDisplayMessageType, OverrideGetFileGroupDisplayMessage -- [SIL.Media] Added FFmpegRunner.FfmpegMinimumVersion property. +- [SIL.Media] Added FFmpegRunner.MinimumVersion property (also used by MediaInfo for FFprobe). ### Changed @@ -83,11 +83,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [SIL.Media] Upgraded irrKlang to v. 1.6. - [SIL.Media] In FFmpegRunner, changed ExtractMp3Audio, ExtractOggAudio, ExtractAudio, and ChangeNumberOfAudioChannels to use LocateAndRememberFFmpeg instead of LocateFFmpeg. This is potentially a breaking change but only in the edge case where an app does not install FFmpeg and the user installs it while running the app. - [SIL.Media] Made the Windows implementation of ISimpleAudioSession more robust in that it will attempt to create an irrKlang-based recorder even if there is no audio output device enabled. +- [SIL.Media] Made FFmpegRunner explicitly a static class (technically a breaking change, though all methods were already static). +- [SIL.Media] Made FFmpegRunner look for the exe on the path before trying to find a version installed for Audacity (which is unlikely to succeed anyway). +- [SIL.Media] Made MediaInfo look for the FFprobe exe in the same location as FFmpeg when the application has specified the location for it or when it was previously located in one of the expected locations. Also made it more robust by making it more likely to find FFprobe (when it is on the system path). ### Fixed - [SIL.Archiving] Fixed typo in RampArchivingDlgViewModel for Ethnomusicology performance collection. - [SIL.Archiving] Changed URLs that used http: to https: in resource EmptyMets.xml. - [SIL.Core.Desktop] Implemented GetDefaultProgramForFileType (as trenamed) in a way that works on Windows 11, Mono (probably) and MacOS (untested). +- [SIL.Media] MediaInfo.HaveNecessaryComponents properly returns true if FFprobe is on the system path. +- [SIL.Media] Made MediaInfo.FFprobeFolder look for and return the folder when first accessed, even if no prior call to the setter or other action had caused it t be found. ### Removed diff --git a/SIL.Core/CommandLineProcessing/CommandLineRunner.cs b/SIL.Core/CommandLineProcessing/CommandLineRunner.cs index a10e8c2d9..5b2e7d88d 100644 --- a/SIL.Core/CommandLineProcessing/CommandLineRunner.cs +++ b/SIL.Core/CommandLineProcessing/CommandLineRunner.cs @@ -90,7 +90,7 @@ public bool Abort(int secondsBeforeTimeout) } /// - /// use this one if you're doing a long running task that you'll have running in a thread, + /// use this one if you're doing a long-running task that you'll have running in a thread, /// so that you need a way to abort it /// public ExecutionResult Start(string exePath, string arguments, Encoding encoding, diff --git a/SIL.Media.Tests/FFmpegRunnerTests.cs b/SIL.Media.Tests/FFmpegRunnerTests.cs index dcae847aa..19952e360 100644 --- a/SIL.Media.Tests/FFmpegRunnerTests.cs +++ b/SIL.Media.Tests/FFmpegRunnerTests.cs @@ -34,7 +34,7 @@ public void HaveNecessaryComponents_NoExplicitMinVersion_ReturnsTrue() [TestCase(4, 9)] public void HaveNecessaryComponents_TwoDigitMinVersion_ReturnsTrue(int major, int minor) { - FFmpegRunner.FfmpegMinimumVersion = new Version(major, minor); + FFmpegRunner.MinimumVersion = new Version(major, minor); Assert.IsTrue(FFmpegRunner.HaveNecessaryComponents); } @@ -42,7 +42,7 @@ public void HaveNecessaryComponents_TwoDigitMinVersion_ReturnsTrue(int major, in [TestCase(5, 0, 0)] public void HaveNecessaryComponents_ThreeDigitMinVersion_ReturnsTrue(int major, int minor, int build) { - FFmpegRunner.FfmpegMinimumVersion = new Version(major, minor, build); + FFmpegRunner.MinimumVersion = new Version(major, minor, build); Assert.IsTrue(FFmpegRunner.HaveNecessaryComponents); } @@ -50,14 +50,14 @@ public void HaveNecessaryComponents_ThreeDigitMinVersion_ReturnsTrue(int major, [TestCase(5, 0, 0, 9)] public void HaveNecessaryComponents_FourDigitMinVersion_ReturnsTrue(int major, int minor, int build, int revision) { - FFmpegRunner.FfmpegMinimumVersion = new Version(major, minor, build, revision); + FFmpegRunner.MinimumVersion = new Version(major, minor, build, revision); Assert.IsTrue(FFmpegRunner.HaveNecessaryComponents); } [Test] public void HaveNecessaryComponents_ReallyHighVersionThatDoesNotExist_ReturnsFalse() { - FFmpegRunner.FfmpegMinimumVersion = new Version(int.MaxValue, int.MaxValue); + FFmpegRunner.MinimumVersion = new Version(int.MaxValue, int.MaxValue); Assert.IsFalse(FFmpegRunner.HaveNecessaryComponents); } diff --git a/SIL.Media.Tests/MediaInfoTests.cs b/SIL.Media.Tests/MediaInfoTests.cs index 0b4710e38..021acd80e 100644 --- a/SIL.Media.Tests/MediaInfoTests.cs +++ b/SIL.Media.Tests/MediaInfoTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using NUnit.Framework; using SIL.IO; using SIL.Media.Tests.Properties; @@ -24,7 +25,35 @@ public void CheckRequirements() [Test] public void HaveNecessaryComponents_ReturnsTrue() { - Assert.IsTrue(MediaInfo.HaveNecessaryComponents); + Assert.IsTrue(MediaInfo.HaveNecessaryComponents, + "FFprobe was expected to have been found on system path or in a known location."); + } + + [TestCase(null)] + [TestCase("")] + public void SetFFprobeFolder_ToNullOrEmpty_HaveNecessaryComponentsReturnsTrue(string presetFolder) + { + MediaInfo.FFprobeFolder = presetFolder; + Assert.IsTrue(MediaInfo.HaveNecessaryComponents, + "FFprobe was expected to have been found on system path or in a known location."); + } + + [Test] + public void SetFFprobeFolder_ToNonexistentFolder_ThrowsDirectoryNotFoundException() + { + Assert.That(() => + { + MediaInfo.FFprobeFolder = "D:\\ThereIsNoWayThi5F0lderShould\\exist"; + }, Throws.Exception.InstanceOf()); + } + + [Test] + public void SetFFprobeFolder_ToFolderWithoutFFprobe_ThrowsFileNotFoundException() + { + Assert.That(() => + { + MediaInfo.FFprobeFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments); + }, Throws.Exception.InstanceOf()); } [Test] diff --git a/SIL.Media/FFmpegRunner.cs b/SIL.Media/FFmpegRunner.cs index 63c4cbc22..3720ca3d3 100644 --- a/SIL.Media/FFmpegRunner.cs +++ b/SIL.Media/FFmpegRunner.cs @@ -1,14 +1,16 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using JetBrains.Annotations; using SIL.CommandLineProcessing; using SIL.IO; using SIL.PlatformUtilities; using SIL.Progress; +using static System.Environment; +using static System.IO.Path; using static System.String; +using Version = System.Version; namespace SIL.Media { @@ -19,25 +21,30 @@ namespace SIL.Media /// maybe figure out how to use that to provide progress reporting, but it's not clear /// that it would be worth it. /// - public class FFmpegRunner + public static class FFmpegRunner { + internal const string kLinuxBinFolder = "/usr/bin"; + internal const string kFFmpeg = "ffmpeg"; private const string kFFmpegExe = "ffmpeg.exe"; - private const string mp3LameCodecArg = "-acodec libmp3lame"; + private const string kMp3LameCodecArg = "-acodec libmp3lame"; /// - /// If your app knows where FFmpeg lives, you can tell us before making any calls. + /// If your app knows where FFmpeg lives, set this before making any calls. + /// Unless the exe is on the system path, this is the full path to the executable, + /// including the executable file itself. /// public static string FFmpegLocation; + /// - /// If your app has a known minimum version of FFMpeg that it will work with, you can set - /// this to prevent this library from attempting to use a version that is not going to meet - /// your requirements. This will be ignored if you set FFmpegLocation, if the ffmpeg - /// installation is based on a Linux package dependency or if FFmpeg is colocated with the - /// applications, since it seems safe to assume that you are not specifying or installing a - /// version that does not satisfy your needs. + /// If your app has a known minimum version of FFMpeg tools that it will work with, you can + /// set this to prevent this library from attempting to use a version that is not going to + /// meet your requirements. This will be ignored if you set FFmpegLocation, if the ffmpeg + /// installation is based on a Linux package dependency, or if FFmpeg is colocated with the + /// applications, since it seems safe to assume that you would not specify or install a + /// version that does not satisfy your needs. Note that this also applies to FFprobe (used + /// in MediaInfo). /// - public static Version FfmpegMinimumVersion; - private static bool? _ffmpegOnPath; + public static Version MinimumVersion; /// /// Find the path to FFmpeg, and remember it (some apps (like SayMore) call FFmpeg a lot) @@ -45,103 +52,78 @@ public class FFmpegRunner /// internal static string LocateAndRememberFFmpeg() { - if (null != FFmpegLocation) //NO! string.empty means we looked and didn't find: string.IsNullOrEmpty(s_ffmpegLocation)) + // Do not change this to IsNullOrEmpty(FFmpegLocation) because Empty means we already + // looked and didn't find it. + if (null != FFmpegLocation) return FFmpegLocation; - FFmpegLocation = LocateFFmpeg() ?? Empty; - return FFmpegLocation; - } - /// - /// FFmpeg will typically be distributed with SIL software on Windows or automatically - /// installed via package dependencies on other platforms, but if something wants to - /// use this library and work with a version of it that the user downloaded or compiled - /// locally, this tries to find where they put it. - /// - /// the path, if found, else null - private static string LocateFFmpeg() - { - if (Platform.IsLinux) - { - //on linux, we can safely assume the package has included the needed dependency - if (File.Exists("/usr/bin/ffmpeg")) - return "/usr/bin/ffmpeg"; - if (File.Exists("/usr/bin/avconv")) - return "/usr/bin/avconv"; // the new name of ffmpeg on Linux - - return null; - } - - string withApplicationDirectory = GetPathToBundledFFmpeg(); - - if (withApplicationDirectory != null && File.Exists(withApplicationDirectory)) - return withApplicationDirectory; + FFmpegLocation = LocateFFmpeg(); + return FFmpegLocation; - var fromChoco = MediaInfo.GetFFmpegFolderFromChocoInstall(kFFmpegExe); - if (fromChoco != null) + // This returns the exe path if found; otherwise Empty. + static string LocateFFmpeg() { - var pathToFFmpeg = Path.Combine(fromChoco, kFFmpegExe); - if (MeetsMinimumVersionRequirement(pathToFFmpeg)) + if (Platform.IsLinux) + { + // On Linux, we can assume the package has included the needed dependency. + var convExePath = Combine(kLinuxBinFolder, kFFmpeg); + if (File.Exists(convExePath)) + return convExePath; + // Try avconv, the new name of ffmpeg on Linux + convExePath = Combine(kLinuxBinFolder, "avconv"); + return File.Exists(convExePath) ? convExePath : Empty; + } + + // On Windows FFmpeg will typically be distributed with the SIL software that + // accompanies the SIL.Media DLL. + var withApplicationDirectory = GetPathToBundledFFmpeg(); + if (withApplicationDirectory != null && File.Exists(withApplicationDirectory)) + return withApplicationDirectory; + + // Failing that, if a program wants to use this library and work with a version of + // it that the user downloaded or compiled locally, this logic tries to find it. + var fromChoco = GetFFmpegFolderFromChocoInstall(kFFmpegExe); + if (fromChoco != null) + { + var pathToFFmpeg = Combine(fromChoco, kFFmpegExe); return pathToFFmpeg; - } - - var progFileDirs = new List { - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) - }; + } - /* We DON'T SUPPORT THIS ONE (it lacks some information on the output, at least as of - * July 2010) - //from http://www.arachneweb.co.uk/software/windows/avchdview/ffmpeg.html - foreach (var path in progFileDirs) - { - var exePath = (Path.Combine(path, "ffmpeg/win32-static/bin/ffmpeg.exe")); - if(File.Exists(exePath)) - return exePath; - } - */ - - // REVIEW: I just followed the instructions in the current version of Audacity for - // installing FFmpeg for Audacity and the result is a folder that does not contain - // ffmpeg.exe. This maybe used to work, but I don't think we'll ever find ffmpeg this - // way now. - //http://manual.audacityteam.org/index.php?title=FAQ:Installation_and_Plug-Ins#installffmpeg - foreach (var path in progFileDirs) - { - var exePath = (Path.Combine(path, "FFmpeg for Audacity", kFFmpegExe)); - if (File.Exists(exePath) && MeetsMinimumVersionRequirement(exePath)) - return exePath; - } - - string ffmpeg = null; - - // Locate may be called multiple times, we don't want to run this command every time. - if (_ffmpegOnPath == null) - { - ffmpeg = Path.GetFileNameWithoutExtension(kFFmpegExe); // Try to just run ffmpeg from the path, if it works then we can use that directly. - _ffmpegOnPath = MeetsMinimumVersionRequirement(ffmpeg); - if (!_ffmpegOnPath.Value) - ffmpeg = null; + if (MeetsMinimumVersionRequirement(kFFmpeg)) + return kFFmpeg; + + // REVIEW: I just followed the instructions in the current version of Audacity for + // installing FFmpeg for Audacity and the result is a folder that does not contain + // ffmpeg.exe. This maybe used to work, but I don't think we'll ever find ffmpeg this + // way now. + // https://support.audacityteam.org/basics/installing-ffmpeg + return new[] { + GetFolderPath(SpecialFolder.ProgramFiles), + GetFolderPath(SpecialFolder.ProgramFilesX86) } + .Select(path => Combine(path, "FFmpeg for Audacity", kFFmpegExe)) + .FirstOrDefault(exePath => File.Exists(exePath) && + MeetsMinimumVersionRequirement(exePath)) ?? Empty; } - return ffmpeg; } - private static bool MeetsMinimumVersionRequirement(string ffmpeg) + internal static bool MeetsMinimumVersionRequirement(string exe) { try { - var version = new Regex(@"ffmpeg version (?\d+\.\d+(\.\d+)?)"); - var results = CommandLineRunner.Run(ffmpeg, "-version", ".", 5, new NullProgress()); + var version = new Regex(GetFileNameWithoutExtension(exe).ToLowerInvariant() + + @" version (?\d+\.\d+(\.\d+)?)"); + var results = CommandLineRunner.Run(exe, "-version", ".", 5, new NullProgress()); var match = version.Match(results.StandardOutput); if (!match.Success) return false; - if (FfmpegMinimumVersion == null) + if (MinimumVersion == null) return true; var actualVersion = Version.Parse(match.Groups["version"].Value); actualVersion = new Version(actualVersion.Major, actualVersion.Minor, actualVersion.Build >= 0 ? actualVersion.Build : 0, actualVersion.Revision >= 0 ? actualVersion.Revision : 0); - return actualVersion >= FfmpegMinimumVersion; + return actualVersion >= MinimumVersion; } catch { @@ -149,11 +131,32 @@ private static bool MeetsMinimumVersionRequirement(string ffmpeg) } } + internal static string GetFFmpegFolderFromChocoInstall(string exeNeeded) + { + try + { + var programData = GetFolderPath(SpecialFolder + .CommonApplicationData); + + var folder = Combine(programData, "chocolatey", "lib", kFFmpeg, "tools", + kFFmpeg, "bin"); + var pathToExe = Combine(folder, exeNeeded); + if (!File.Exists(pathToExe)|| !MeetsMinimumVersionRequirement(pathToExe)) + folder = null; + return folder; + } + catch (Exception e) + { + Console.WriteLine(e); + return null; + } + } + private static string GetPathToBundledFFmpeg() { try { - return FileLocationUtilities.GetFileDistributedWithApplication("ffmpeg", kFFmpegExe); + return FileLocationUtilities.GetFileDistributedWithApplication(kFFmpeg, kFFmpegExe); } catch (Exception) { @@ -164,7 +167,7 @@ private static string GetPathToBundledFFmpeg() /// /// Returns false if it can't find ffmpeg /// - public static bool HaveNecessaryComponents => LocateFFmpeg() != null; + public static bool HaveNecessaryComponents => LocateAndRememberFFmpeg() != null; private static ExecutionResult NoFFmpeg => new ExecutionResult { StandardError = "Could not locate FFmpeg" }; @@ -183,7 +186,7 @@ public static ExecutionResult ExtractMp3Audio(string inputPath, string outputPat if (LocateAndRememberFFmpeg() == null) return NoFFmpeg; - var arguments = $"-i \"{inputPath}\" -vn {mp3LameCodecArg} -ac {channels} \"{outputPath}\""; + var arguments = $"-i \"{inputPath}\" -vn {kMp3LameCodecArg} -ac {channels} \"{outputPath}\""; var result = RunFFmpeg(arguments, progress); // Hide a meaningless error produced by some versions of liblame @@ -375,7 +378,7 @@ public static ExecutionResult MakeLowQualityCompressedAudio(string inputPath, st if (IsNullOrEmpty(LocateAndRememberFFmpeg())) return NoFFmpeg; - var arguments = $"-i \"{inputPath}\" {mp3LameCodecArg} -ac 1 -ar 8000 \"{outputPath}\""; + var arguments = $"-i \"{inputPath}\" {kMp3LameCodecArg} -ac 1 -ar 8000 \"{outputPath}\""; var result = RunFFmpeg(arguments, progress); @@ -415,7 +418,7 @@ public static ExecutionResult MakeLowQualitySmallVideo(string inputPath, string // isn't working: var arguments = "-i \"" + inputPath + "\" -vcodec mpeg4 -s 160x120 -b 800 -acodec libmp3lame -ar 22050 -ab 32k -ac 1 \"" + outputPath + "\""; var arguments = $"-i \"{inputPath}\" -vcodec mpeg4 -s 160x120 -b 800 " + - $"{mp3LameCodecArg} -ar 22050 -ab 32k -ac 1 "; + $"{kMp3LameCodecArg} -ar 22050 -ab 32k -ac 1 "; if (maxSeconds > 0) arguments += $" -t {maxSeconds} "; arguments += $"\"{outputPath}\""; @@ -466,11 +469,11 @@ public static ExecutionResult MakeLowQualitySmallPicture(string inputPath, strin private static ExecutionResult RunFFmpeg(string arguments, IProgress progress) { - progress.WriteMessage("ffmpeg " + arguments); + progress.WriteMessage($"{GetFileNameWithoutExtension(LocateAndRememberFFmpeg())} {arguments}"); const int timeout = 600; // 60 * 10 = 10 minutes var result = CommandLineRunner.Run(LocateAndRememberFFmpeg(), arguments, - Environment.CurrentDirectory, timeout, progress); + CurrentDirectory, timeout, progress); progress.WriteVerbose(result.StandardOutput); diff --git a/SIL.Media/MediaInfo.cs b/SIL.Media/MediaInfo.cs index fff240eec..a2ca74e2b 100644 --- a/SIL.Media/MediaInfo.cs +++ b/SIL.Media/MediaInfo.cs @@ -8,6 +8,7 @@ using SIL.Reporting; using static System.String; using static SIL.IO.FileLocationUtilities; +using static SIL.Media.FFmpegRunner; namespace SIL.Media { @@ -16,14 +17,16 @@ namespace SIL.Media /// public class MediaInfo { - private static string FFprobeExe => Platform.IsWindows ? "ffprobe.exe" : "ffprobe"; + private const string kFFprobe = "ffprobe"; + private static string FFprobeExe => Platform.IsWindows ? "ffprobe.exe" : kFFprobe; private static string s_ffProbeFolder; + private static bool s_foundFFProbeOnPath; /// /// Returns false if it can't find FFprobe /// - public static bool HaveNecessaryComponents => !IsNullOrEmpty(LocateAndRememberFFprobe()); + public static bool HaveNecessaryComponents => !IsNullOrEmpty(LocateAndRememberFFprobe()) || s_foundFFProbeOnPath; /// /// The folder where FFprobe should be found. An application can use this to set @@ -32,20 +35,26 @@ public class MediaInfo /// for other purposes, this folder will also be used for those calls. /// /// Folder does not exist - /// Folder does not contain the ffprobe + /// Folder does not contain the FFprobe /// executable /// FFMpegCore failed to set the requested /// FFprobe folder. - /// If set to , indicates to this library that - /// FFprobe is not installed, and therefore methods to retrieve media info will - /// fail unconditionally (i.e. they will throw an exception) + /// if FFprobe is not installed or is on the path. + /// If this returns , clients can use + /// to know whether FFprobe was found on the system + /// path. If not, attempts to retrieve media info will fail unconditionally (i.e. they will + /// throw an exception) [PublicAPI] public static string FFprobeFolder { - get => s_ffProbeFolder; + get => LocateAndRememberFFprobe(); set { - if (value != Empty) + if (IsNullOrEmpty(value)) + { + s_foundFFProbeOnPath = MeetsMinimumVersionRequirement(FFprobeExe); + } + else { if (!Directory.Exists(value)) throw new DirectoryNotFoundException("Directory not found: " + value); @@ -77,73 +86,74 @@ public static string FFprobeFolder /// private static string LocateAndRememberFFprobe() { - if (null != FFprobeFolder) - return FFprobeFolder; + if (null != s_ffProbeFolder) + return s_ffProbeFolder; try { - FFprobeFolder = GetPresumedFFprobeFolder(); + s_ffProbeFolder = GetPresumedFFprobeFolder(); + return s_ffProbeFolder; } catch (Exception e) { Console.WriteLine(e); return null; } - return FFprobeFolder; - } - - /// - /// On Windows, FFprobe will typically be distributed with SIL software, but if something - /// wants to use this library and work with a version of it that the user downloaded or - /// compiled locally, this tries to find where they put it. On other platforms, ffprobe - /// should be installed in the standard location by the package manager. - /// - /// A folder where FFprobe can be found; else - private static string GetPresumedFFprobeFolder() - { - var folder = GlobalFFOptions.Current.BinaryFolder; - // On Linux, we assume the package has included the needed dependency. - // ENHANCE: Make it possible to find in a non-default location - if (!Platform.IsWindows) + // On Windows, FFprobe will typically be distributed with SIL software, but if something + // wants to use this library and work with a version of it that the user downloaded or + // compiled locally, this tries to find where they put it. On other platforms, FFprobe + // should be installed in the standard location by the package manager. + // Returns a folder where FFprobe can be found; else Empty. + static string GetPresumedFFprobeFolder() { - if (IsNullOrEmpty(folder) && File.Exists(Path.Combine("/usr/bin", FFprobeExe))) - folder = "/usr/bin"; - } - else - { - if (IsNullOrEmpty(folder) || !File.Exists(Path.Combine(folder, FFprobeExe))) - { - var withApplicationDirectory = - GetFileDistributedWithApplication(true, "ffmpeg", FFprobeExe) ?? - GetFileDistributedWithApplication(true, "ffprobe", FFprobeExe); + var folder = GlobalFFOptions.Current.BinaryFolder; - folder = (!IsNullOrEmpty(withApplicationDirectory) ? - Path.GetDirectoryName(withApplicationDirectory) : - GetFFmpegFolderFromChocoInstall(FFprobeExe)) ?? Empty; + // On Linux, we assume the package has included the needed dependency. + // ENHANCE: Make it possible to find in a non-default location + if (!Platform.IsWindows) + { + if (IsNullOrEmpty(folder) && + File.Exists(Path.Combine(kLinuxBinFolder, FFprobeExe))) + folder = kLinuxBinFolder; } - } + else + { + if (IsNullOrEmpty(folder) || !File.Exists(Path.Combine(folder, FFprobeExe))) + { + // If the application explicitly set FFmpegLocation or we can find it + // by the normal logic, then use the ffProbe found in that same folder. + if (LocateAndRememberFFmpeg() != null) + { + folder = Path.GetDirectoryName(FFmpegLocation); + if (!IsNullOrEmpty(folder) && + File.Exists(Path.Combine(folder, FFprobeExe))) + return folder; + } - return folder; - } + // Most of this logic is likely redundant since LocateAndRememberFFmpeg should + // have already checked these places, but there's a faint chance that FFprobe + // is there even is FFmpeg is not. + var withApplicationDirectory = + GetFileDistributedWithApplication(true, kFFmpeg, FFprobeExe) ?? + GetFileDistributedWithApplication(true, kFFprobe, FFprobeExe); - internal static string GetFFmpegFolderFromChocoInstall(string exeNeeded) - { - try - { - var programData = Environment.GetFolderPath(Environment.SpecialFolder - .CommonApplicationData); + folder = (!IsNullOrEmpty(withApplicationDirectory) ? + Path.GetDirectoryName(withApplicationDirectory) : + GetFFmpegFolderFromChocoInstall(FFprobeExe)) ?? Empty; + + if (folder == Empty) + { + if (MeetsMinimumVersionRequirement(FFprobeExe)) + { + s_foundFFProbeOnPath = true; + return Empty; + } + } + } + } - var folder = Path.Combine(programData, "chocolatey", "lib", "ffmpeg", "tools", - "ffmpeg", "bin"); - if (!File.Exists(Path.Combine(folder, exeNeeded))) - folder = null; return folder; } - catch (Exception e) - { - Console.WriteLine(e); - return null; - } } [PublicAPI] @@ -240,8 +250,8 @@ internal AudioInfo(IMediaAnalysis mediaAnalysis) /// /// Note that since a media file might contain multiple audio and video tracks - /// and they might start and stop at different times, the total duration reported - /// by could be greater than this duration. + /// which might start and stop at different times, the total duration reported + /// by could be greater than this duration. /// [PublicAPI] public TimeSpan Duration { get; } @@ -269,8 +279,8 @@ internal VideoInfo(IMediaAnalysis mediaAnalysis) /// /// Note that since a media file might contain multiple audio and video tracks - /// and they might start and stop at different times, the total duration reported - /// by could be greater than this duration. + /// which might start and stop at different times, the total duration reported + /// by could be greater than this duration. /// [PublicAPI] public TimeSpan Duration { get; }