diff --git a/src/Aurio.FFmpeg.UnitTest/Aurio.FFmpeg.UnitTest.csproj b/src/Aurio.FFmpeg.UnitTest/Aurio.FFmpeg.UnitTest.csproj index 24ec5cd7..ced1c761 100644 --- a/src/Aurio.FFmpeg.UnitTest/Aurio.FFmpeg.UnitTest.csproj +++ b/src/Aurio.FFmpeg.UnitTest/Aurio.FFmpeg.UnitTest.csproj @@ -18,6 +18,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/src/Aurio.FFmpeg.UnitTest/FFmpegSourceStreamTests.cs b/src/Aurio.FFmpeg.UnitTest/FFmpegSourceStreamTests.cs new file mode 100644 index 00000000..c0bee4d3 --- /dev/null +++ b/src/Aurio.FFmpeg.UnitTest/FFmpegSourceStreamTests.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using Xunit; + +namespace Aurio.FFmpeg.UnitTest +{ + public class FFmpegSourceStreamTests + { + [Fact] + public void CreateWaveProxy_ProxyFileInfoIsNull() + { + FileInfo fileInfo = new("X:\\test.file"); + FileInfo? proxyFileInfo = null; + + void act() => FFmpegSourceStream.CreateWaveProxy(fileInfo, proxyFileInfo); + + Assert.Throws(act); + } + + [SkippableFact] + public void SuggestWaveProxyFileInfo_WithoutDirectory_Windows() + { + Skip.IfNot(OperatingSystem.IsWindows()); + + var fileInfo = new FileInfo("X:\\folder\\file.wav"); + + var proxyFileInfo = FFmpegSourceStream.SuggestWaveProxyFileInfo(fileInfo); + + Assert.Equal("X:\\folder\\file.wav.ffproxy.wav", proxyFileInfo.FullName); + } + + [SkippableFact] + public void SuggestWaveProxyFileInfo_WithoutDirectory_Unix() + { + Skip.If(OperatingSystem.IsWindows()); + + var fileInfo = new FileInfo("/folder/file.wav"); + + var proxyFileInfo = FFmpegSourceStream.SuggestWaveProxyFileInfo(fileInfo); + + Assert.Equal("/folder/file.wav.ffproxy.wav", proxyFileInfo.FullName); + } + + [SkippableFact] + public void SuggestWaveProxyFileInfo_WithDirectory_Windows() + { + Skip.IfNot(OperatingSystem.IsWindows()); + + var fileInfo = new FileInfo("X:\\folder\\file.wav"); + var directoryInfo = new DirectoryInfo("Y:\\temp\\dir"); + + var proxyFileInfo = FFmpegSourceStream.SuggestWaveProxyFileInfo( + fileInfo, + directoryInfo + ); + + Assert.Equal( + "Y:\\temp\\dir\\87c18cf1c8d8e07552df4ecc1ef629995fca9c59ad47ccf7eb4816de33590af7.ffproxy.wav", + proxyFileInfo.FullName + ); + } + + [SkippableFact] + public void SuggestWaveProxyFileInfo_WithDirectory_Unix() + { + Skip.If(OperatingSystem.IsWindows()); + + var fileInfo = new FileInfo("/folder/file.wav"); + var directoryInfo = new DirectoryInfo("/temp/dir"); + + var proxyFileInfo = FFmpegSourceStream.SuggestWaveProxyFileInfo( + fileInfo, + directoryInfo + ); + + Assert.Equal( + "/temp/dir/e2c5b9282d156c5bdbf95639ca2ce2516b096e4828b7aa16620cb317cc90b3d9.ffproxy.wav", + proxyFileInfo.FullName + ); + } + } +} diff --git a/src/Aurio.FFmpeg/FFmpegAudioStreamFactory.cs b/src/Aurio.FFmpeg/FFmpegAudioStreamFactory.cs index e063ce1d..54df6276 100644 --- a/src/Aurio.FFmpeg/FFmpegAudioStreamFactory.cs +++ b/src/Aurio.FFmpeg/FFmpegAudioStreamFactory.cs @@ -31,6 +31,8 @@ public FFmpegAudioStreamFactory() public IAudioStream OpenFile(FileInfo fileInfo, FileInfo proxyFileInfo = null) { + proxyFileInfo ??= FFmpegSourceStream.SuggestWaveProxyFileInfo(fileInfo); + if (FFmpegSourceStream.WaveProxySuggested(fileInfo)) { Console.WriteLine("File format with known seek problems, creating proxy file..."); diff --git a/src/Aurio.FFmpeg/FFmpegSourceStream.cs b/src/Aurio.FFmpeg/FFmpegSourceStream.cs index b0e84c06..67a817cd 100644 --- a/src/Aurio.FFmpeg/FFmpegSourceStream.cs +++ b/src/Aurio.FFmpeg/FFmpegSourceStream.cs @@ -27,6 +27,8 @@ namespace Aurio.FFmpeg { public class FFmpegSourceStream : IAudioStream { + public const string ProxyFileExtension = ".ffproxy.wav"; + private Stream sourceStream; private FFmpegReader reader; private AudioProperties properties; @@ -287,6 +289,11 @@ public void Dispose() public static FileInfo CreateWaveProxy(FileInfo fileInfo, FileInfo proxyFileInfo) { + if (proxyFileInfo == null) + { + throw new ArgumentNullException(nameof(proxyFileInfo)); + } + if (proxyFileInfo.Exists) { Console.WriteLine("Proxy already existing, using " + proxyFileInfo.Name); @@ -339,39 +346,16 @@ out Type type } /// - /// Creates a Wave format proxy file in the same directory and with the same name as the specified file, - /// if no storage directory is specified (i.e. if it is null). If a storage directory is specified, the proxy - /// file will be stored in the specified directory with a hashed file name to avoid name collisions and - /// file overwrites. The story directory option is convenient for the usage of temporary or working directories. + /// Creates a Wave format proxy file for the given file and optional directory according + /// to . /// /// the file for which a proxy file should be created /// optional directory where the proxy file will be stored, can be null /// the FileInfo of the proxy file public static FileInfo CreateWaveProxy(FileInfo fileInfo, DirectoryInfo storageDirectory) { - if (storageDirectory == null) - { - // Without a storage directory, store the proxy file beside the original file - var proxyFileInfo = new FileInfo(fileInfo.FullName + ".ffproxy.wav"); - return CreateWaveProxy(fileInfo, proxyFileInfo); - } - else - { - // With a storage directory specified, store the proxy file with a hashed name - // (to avoid name collision / overwrites) in the target directory (e.g. a temp or working directory) - using (var sha256 = SHA256.Create()) - { - byte[] hash = sha256.ComputeHash(Encoding.Unicode.GetBytes(fileInfo.FullName)); - string hashString = BitConverter - .ToString(hash) - .Replace("-", "") - .ToLowerInvariant(); - var proxyFileInfo = new FileInfo( - Path.Combine(storageDirectory.FullName, hashString + ".ffproxy.wav") - ); - return CreateWaveProxy(fileInfo, proxyFileInfo); - } - } + var proxyFileInfo = SuggestWaveProxyFileInfo(fileInfo, storageDirectory); + return CreateWaveProxy(fileInfo, proxyFileInfo); } /// @@ -399,6 +383,42 @@ public static bool WaveProxySuggested(FileInfo fileInfo) ); } + /// + /// Creates a proxy file info for the provided file with the . + /// + /// If a storage directory is specified, the proxy file will be located in the specified directory + /// with a hashed file name to avoid name collisions. This option is is convenient when using + /// temporary or working directories. + /// + /// If no storage diretory is specified, the proxy file will be located in the same directory and + /// with the same name as the specified file. + /// + /// the file for which a proxy file should be created + /// optional directory where the proxy file will be stored (can be null) + /// the FileInfo of the suggested proxy file + public static FileInfo SuggestWaveProxyFileInfo( + FileInfo fileInfo, + DirectoryInfo storageDirectory = null + ) + { + if (storageDirectory == null) + { + // Without a storage directory, store the proxy file beside the original file + return new FileInfo(fileInfo.FullName + ProxyFileExtension); + } + else + { + // With a storage directory specified, store the proxy file with a hashed name + // (to avoid name collision / overwrites) in the target directory (e.g. a temp or working directory) + using var sha256 = SHA256.Create(); + byte[] hash = sha256.ComputeHash(Encoding.Unicode.GetBytes(fileInfo.FullName)); + string hashString = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + return new FileInfo( + Path.Combine(storageDirectory.FullName, hashString + ProxyFileExtension) + ); + } + } + public class FileNotSeekableException : Exception { public FileNotSeekableException()