From ec831654df8228157d364cb1528397d4b86c44f9 Mon Sep 17 00:00:00 2001 From: Mahnoosh Kholghi Date: Thu, 31 May 2018 10:13:18 +1000 Subject: [PATCH 1/3] Editting temporary file dir and file time zone --- src/AnalysisPrograms/AudioCutter.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/AnalysisPrograms/AudioCutter.cs b/src/AnalysisPrograms/AudioCutter.cs index 2d4689aa9..882b11e15 100644 --- a/src/AnalysisPrograms/AudioCutter.cs +++ b/src/AnalysisPrograms/AudioCutter.cs @@ -118,6 +118,12 @@ public class Arguments : SubCommandBase ShortName = "p")] public bool Parallel { get; set; } = true; + [Option( + CommandOptionType.SingleValue, + Description = "TimeSpan offset hint required if file names do not contain time zone info. NO DEFAULT IS SET", + ShortName = "z")] + public TimeSpan? TimeSpanOffsetHint { get; set; } + public override Task Execute(CommandLineApplication app) { AudioCutter.Execute(this); @@ -185,11 +191,11 @@ public static void Execute(Arguments arguments) AnalysisMinSegmentDuration = TimeSpan.FromSeconds(arguments.SegmentDurationMinimum), SegmentOverlapDuration = TimeSpan.FromSeconds(arguments.SegmentOverlap), AnalysisTargetSampleRate = arguments.SampleRate, - AnalysisTempDirectory = arguments.TemporaryFilesDir.ToDirectoryInfo(), + AnalysisTempDirectory = (arguments.TemporaryFilesDir ?? arguments.OutputDir).ToDirectoryInfo(), }; // create segments from file - var fileSegment = new FileSegment(arguments.InputFile.ToFileInfo(), TimeAlignment.None) + var fileSegment = new FileSegment(arguments.InputFile.ToFileInfo(), TimeAlignment.None, dateBehavior: FileSegment.FileDateBehavior.None) { SegmentStartOffset = TimeSpan.FromSeconds(arguments.StartOffset), }; From 0f00ad4cbedd6ced6f59245e1125a85071ac3f83 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Mon, 11 Jun 2018 15:32:44 +1000 Subject: [PATCH 2/3] AudioCutter improvements Adds: unit tests for audio cutter Improves: AudioCutter now uses async methods Fixes: - Audio Cutter now honours the min length argument - Audio Cutter uses the modern segment naming format (seconds in filenames) --- src/Acoustics.Tools/AudioFilePreparer.cs | 5 +- src/AnalysisBase/AnalysisSettings.cs | 1 + src/AnalysisPrograms/AudioCutter.cs | 54 +++--- .../SourcePreparers/LocalSourcePreparer.cs | 88 ++++++++-- .../SourcePreparers/RemoteSourcePreparer.cs | 10 +- tests/Acoustics.Test/Acoustics.Test.csproj | 1 + .../AnalysisPrograms/AudioCutterTests.cs | 165 ++++++++++++++++++ .../LocalSourcePreparerTests.cs | 24 ++- .../Acoustics.Test/TestHelpers/Assertions.cs | 18 ++ 9 files changed, 321 insertions(+), 45 deletions(-) create mode 100644 tests/Acoustics.Test/AnalysisPrograms/AudioCutterTests.cs diff --git a/src/Acoustics.Tools/AudioFilePreparer.cs b/src/Acoustics.Tools/AudioFilePreparer.cs index e0123f048..b97ea8bb4 100644 --- a/src/Acoustics.Tools/AudioFilePreparer.cs +++ b/src/Acoustics.Tools/AudioFilePreparer.cs @@ -76,14 +76,15 @@ public static AudioUtilityModifiedInfo PrepareFile( FileInfo source, string outputMediaType, AudioUtilityRequest request, - DirectoryInfo temporaryFilesDirectory) + DirectoryInfo temporaryFilesDirectory, + bool oldFormat = true) { var outputFileName = GetFileName( source.Name, outputMediaType, request.OffsetStart, request.OffsetEnd, - oldFormat: true); + oldFormat: oldFormat); var outputFile = new FileInfo(Path.Combine(outputDirectory.FullName, outputFileName)); diff --git a/src/AnalysisBase/AnalysisSettings.cs b/src/AnalysisBase/AnalysisSettings.cs index 73aa14b77..105bbba4d 100644 --- a/src/AnalysisBase/AnalysisSettings.cs +++ b/src/AnalysisBase/AnalysisSettings.cs @@ -156,6 +156,7 @@ public DirectoryInfo AnalysisTempDirectoryFallback /// Gets or sets the minimum audio file duration the analysis can process. /// This is the min duration without including overlap. Overlap will be added. /// This should be set to an initial value by an analysis. + /// This value is used in /// TODO: a chunk of audio without the overlap is a 'segment step'. /// public TimeSpan AnalysisMinSegmentDuration { get; set; } diff --git a/src/AnalysisPrograms/AudioCutter.cs b/src/AnalysisPrograms/AudioCutter.cs index 882b11e15..461f11bc1 100644 --- a/src/AnalysisPrograms/AudioCutter.cs +++ b/src/AnalysisPrograms/AudioCutter.cs @@ -74,13 +74,13 @@ public class Arguments : SubCommandBase public double? EndOffset { get; set; } [Option( - Description = "The minimum duration of a segmented audio file (in seconds, defaults to 10; must be within [0,3600]).", + Description = "The minimum duration of a segmented audio file (in seconds, defaults to 5; must be within [0, 3600]).", ShortName = "")] [InRange(1, 3600)] - public double SegmentDurationMinimum { get; set; } = 10; + public double SegmentDurationMinimum { get; set; } = 5; [Option( - Description = "The duration of a segmented audio file (in seconds, defaults to 60; must be within [0,3600]).", + Description = "The duration of a segmented audio file (in seconds, defaults to 60; must be within [0, 3600]).", ShortName = "d")] [InRange(1, 3600)] public double SegmentDuration { get; set; } = 60; @@ -98,7 +98,7 @@ public class Arguments : SubCommandBase public string SegmentFileExtension { get; set; } = "wav"; [Option( - Description = "The sample rate for segmented audio files (in hertz, defaults to 22050; valid values are 17640, 22050, 44100).", + Description = "The sample rate for segmented audio files (in hertz, defaults to 22050; valid values are 8000, 17640, 22050, 44100, 48000, 96000).", ShortName = "r")] [InRange(8000, 96000)] public int SampleRate { get; set; } = 22050; @@ -118,16 +118,9 @@ public class Arguments : SubCommandBase ShortName = "p")] public bool Parallel { get; set; } = true; - [Option( - CommandOptionType.SingleValue, - Description = "TimeSpan offset hint required if file names do not contain time zone info. NO DEFAULT IS SET", - ShortName = "z")] - public TimeSpan? TimeSpanOffsetHint { get; set; } - public override Task Execute(CommandLineApplication app) { - AudioCutter.Execute(this); - return this.Ok(); + return AudioCutter.Execute(this); } protected override ValidationResult OnValidate(ValidationContext context, CommandLineContext appContext) @@ -172,7 +165,7 @@ protected override ValidationResult OnValidate(ValidationContext context, Comman } } - public static void Execute(Arguments arguments) + public static async Task Execute(Arguments arguments) { if (arguments == null) { @@ -181,7 +174,7 @@ public static void Execute(Arguments arguments) var sw = new Stopwatch(); sw.Start(); - ISourcePreparer sourcePreparer = new LocalSourcePreparer(); + ISourcePreparer sourcePreparer = new LocalSourcePreparer(filterShortSegments: true, useOldNamingFormat: false); //create analysis settings using arguments AnalysisSettings settings = new AnalysisSettings() @@ -219,21 +212,25 @@ public static void Execute(Arguments arguments) } else { - RunSequential(fileSegments, sourcePreparer, settings, arguments); + var runTime = await RunSequential(fileSegments, sourcePreparer, settings, arguments); } sw.Stop(); LoggedConsole.WriteLine("Took {0}. Done.", sw.Elapsed); + return ExceptionLookup.Ok; } - private static void RunSequential(List> fileSegments, ISourcePreparer sourcePreparer, AnalysisSettings settings, Arguments arguments) + private static async Task RunSequential(List> fileSegments, ISourcePreparer sourcePreparer, AnalysisSettings settings, Arguments arguments) { var totalItems = fileSegments.Count; + var totalTime = 0.0; for (var index = 0; index < fileSegments.Count; index++) { var item = fileSegments[index]; - CreateSegment(sourcePreparer, item, settings, arguments, index + 1, totalItems, arguments.MixDownToMono); + totalTime += await CreateSegment(sourcePreparer, item, settings, arguments, index + 1, totalItems, arguments.MixDownToMono); } + + return totalTime; } private static void RunParallel(List> fileSegments, ISourcePreparer sourcePreparer, AnalysisSettings settings, Arguments arguments) @@ -246,11 +243,12 @@ private static void RunParallel(List> fileSegments, ISourcePr var item1 = item; int index1 = Convert.ToInt32(index); - CreateSegment(sourcePreparer, item1, settings, arguments, index1 + 1, totalItems, arguments.MixDownToMono); + // call create segment synchronously + CreateSegment(sourcePreparer, item1, settings, arguments, index1 + 1, totalItems, arguments.MixDownToMono).Wait(); }); } - private static void CreateSegment( + private static async Task CreateSegment( ISourcePreparer sourcePreparer, ISegment fileSegment, AnalysisSettings settings, @@ -259,7 +257,12 @@ private static void CreateSegment( int itemCount, bool mixDownToMono) { - var task = sourcePreparer.PrepareFile( + var timer = Stopwatch.StartNew(); + + FileSegment preparedFile; + try + { + preparedFile = await sourcePreparer.PrepareFile( arguments.OutputDir.ToDirectoryInfo(), fileSegment, settings.SegmentMediaType, @@ -267,15 +270,20 @@ private static void CreateSegment( settings.AnalysisTempDirectory, null, mixDownToMono); - - task.Wait(120.Seconds()); - var preparedFile = task.Result; + } + catch (IOException ioex) + { + LoggedConsole.WriteError($"Failed to cut segment {itemNumber} of {itemCount}:" + ioex.Message); + return double.NaN; + } LoggedConsole.WriteLine( "Created segment {0} of {1}: {2}", itemNumber, itemCount, preparedFile.SourceMetadata.Identifier); + + return timer.Elapsed.TotalSeconds; } } } diff --git a/src/AnalysisPrograms/SourcePreparers/LocalSourcePreparer.cs b/src/AnalysisPrograms/SourcePreparers/LocalSourcePreparer.cs index 7c2234a1b..b7c5f1124 100644 --- a/src/AnalysisPrograms/SourcePreparers/LocalSourcePreparer.cs +++ b/src/AnalysisPrograms/SourcePreparers/LocalSourcePreparer.cs @@ -23,6 +23,14 @@ namespace AnalysisPrograms.SourcePreparers public class LocalSourcePreparer : ISourcePreparer { private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly bool useOldNamingFormat; + private readonly bool filterShortSegments; + + public LocalSourcePreparer(bool filterShortSegments = false, bool useOldNamingFormat = true) + { + this.filterShortSegments = filterShortSegments; + this.useOldNamingFormat = useOldNamingFormat; + } /// /// Prepare an audio file. This will be a single segment of a larger audio file, modified based on the analysisSettings. @@ -73,7 +81,8 @@ public async Task PrepareFile( sourceFileInfo, outputMediaType, request, - TempFileHelper.TempDir()); + TempFileHelper.TempDir(), + oldFormat: this.useOldNamingFormat); return new FileSegment( preparedFile.TargetInfo.SourceFile, @@ -148,7 +157,7 @@ public IEnumerable> CalculateSegments( var analysisSegmentMaxDuration = settings.AnalysisMaxSegmentDuration?.TotalMilliseconds ?? fileSegmentDuration; - var analysisSegmentMinDuration = settings.AnalysisMinSegmentDuration.TotalMilliseconds; + var analysisSegmentMinDuration = this.filterShortSegments ? settings.AnalysisMinSegmentDuration : (TimeSpan?)null; // segment into exact chunks - all but the last chunk will be equal to the max duration var segments = AudioFilePreparer.DivideExactLeaveLeftoversAtEnd( @@ -164,8 +173,19 @@ public IEnumerable> CalculateSegments( { Log.Debug($"Generated fractional segment for time alignment ({startOffset} - {startOffset + startDelta})"); var startAlignDelta = Convert.ToInt64(startDelta.TotalMilliseconds); - yield return - (ISegment)CreateSegment(ref aggregate, startAlignDelta, fileSegment, startOffset, endOffset, overlap); + + if (TryCreateSegment( + ref aggregate, + startAlignDelta, + fileSegment, + startOffset, + endOffset, + overlap, + analysisSegmentMinDuration, + out var validFileSegment)) + { + yield return (ISegment)validFileSegment; + } } else { @@ -176,8 +196,18 @@ public IEnumerable> CalculateSegments( // yield each normal segment foreach (long offset in segments) { - yield return - (ISegment)CreateSegment(ref aggregate, offset, fileSegment, startOffset, endOffset, overlap); + if (TryCreateSegment( + ref aggregate, + offset, + fileSegment, + startOffset, + endOffset, + overlap, + analysisSegmentMinDuration, + out var validFileSegment)) + { + yield return (ISegment)validFileSegment; + } } // include fractional segment cut from time alignment @@ -186,19 +216,32 @@ public IEnumerable> CalculateSegments( { Log.Debug($"Generated fractional segment for time alignment ({endOffset - endDelta} - {endOffset})"); var endAlignDelta = Convert.ToInt64(endDelta.TotalMilliseconds); - yield return - (ISegment)CreateSegment(ref aggregate, endAlignDelta, fileSegment, startOffset, endOffset, overlap); + + if (TryCreateSegment( + ref aggregate, + endAlignDelta, + fileSegment, + startOffset, + endOffset, + overlap, + analysisSegmentMinDuration, + out var validFileSegment)) + { + yield return (ISegment)validFileSegment; + } } } } - internal static ISegment CreateSegment( + internal static bool TryCreateSegment( ref long aggregate, - long offset, + in long offset, ISegment currentSegment, - TimeSpan startOffset, - TimeSpan endOffset, - TimeSpan overlap) + in TimeSpan startOffset, + in TimeSpan endOffset, + in TimeSpan overlap, + TimeSpan? minimumDuration, + out ISegment newSegment) { var newStart = startOffset.Add(TimeSpan.FromMilliseconds(aggregate)); @@ -217,9 +260,23 @@ internal static ISegment CreateSegment( var newEnd = segmentEndOffset; + // The minimum segment filtering is usually taken care of in AnalysisCoordinator. + // However some code does not use AnalysisCoordinator so we duplicate the functionality here. + if (minimumDuration.HasValue) + { + if ((newEnd - newStart) < minimumDuration) + { + Log.Warn( + $"Omitting short segment {newStart}–{newEnd} because it is less than the minimum {minimumDuration}"); + newSegment = null; + return false; + } + } + // So we aren't actually cutting any files, rather we're preparing to cut files. // Thus the clone the object and set new offsets. - return currentSegment.SplitSegment(newStart.TotalSeconds, newEnd.TotalSeconds); + newSegment = currentSegment.SplitSegment(newStart.TotalSeconds, newEnd.TotalSeconds); + return true; } /// @@ -274,7 +331,8 @@ public async Task PrepareFile( segment.Source, outputMediaType, request, - temporaryFilesDirectory); + temporaryFilesDirectory, + oldFormat: this.useOldNamingFormat); return new FileSegment( preparedFile.TargetInfo.SourceFile, diff --git a/src/AnalysisPrograms/SourcePreparers/RemoteSourcePreparer.cs b/src/AnalysisPrograms/SourcePreparers/RemoteSourcePreparer.cs index e9f76a2b3..fecf6329b 100644 --- a/src/AnalysisPrograms/SourcePreparers/RemoteSourcePreparer.cs +++ b/src/AnalysisPrograms/SourcePreparers/RemoteSourcePreparer.cs @@ -140,13 +140,19 @@ public IEnumerable> CalculateSegments( // yield each normal segment foreach (long offset in segments) { - yield return LocalSourcePreparer.CreateSegment( + // The null for minimum means, do not filter short segments. Short filtering is done in AnalysisCoordinator + if (LocalSourcePreparer.TryCreateSegment( ref aggregate, offset, segment, startOffset, endOffset, - overlap); + overlap, + null /*settings.AnalysisMinSegmentDuration*/, + out var validFileSegment)) + { + yield return (ISegment)validFileSegment; + } } } } diff --git a/tests/Acoustics.Test/Acoustics.Test.csproj b/tests/Acoustics.Test/Acoustics.Test.csproj index b0bba2a5b..94ed4fbdc 100644 --- a/tests/Acoustics.Test/Acoustics.Test.csproj +++ b/tests/Acoustics.Test/Acoustics.Test.csproj @@ -307,6 +307,7 @@ stylecop.json + Designer diff --git a/tests/Acoustics.Test/AnalysisPrograms/AudioCutterTests.cs b/tests/Acoustics.Test/AnalysisPrograms/AudioCutterTests.cs new file mode 100644 index 000000000..9940ec24b --- /dev/null +++ b/tests/Acoustics.Test/AnalysisPrograms/AudioCutterTests.cs @@ -0,0 +1,165 @@ +// +// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group). +// + +namespace Acoustics.Test.AnalysisPrograms +{ + using System; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using global::AnalysisPrograms; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using TestHelpers; + + [TestClass] + public class AudioCutterTests : OutputDirectoryTest + { + private static readonly Func FileInfoMapper = info => info.Name; + private readonly string testfile = PathHelper.ResolveAssetPath("f969b39d-2705-42fc-992c-252a776f1af3_090705-0600.wv"); + + [TestMethod] + public async Task TestAudioCutterSimple() + { + await AudioCutter.Execute(new AudioCutter.Arguments + { + InputFile = this.testfile, + OutputDir = this.outputDirectory.FullName, + Parallel = false, + }); + + this.CommonAssertions(this.outputDirectory, "wav"); + } + + [TestMethod] + public async Task TestAudioCutterParallel() + { + await AudioCutter.Execute(new AudioCutter.Arguments + { + InputFile = this.testfile, + OutputDir = this.outputDirectory.FullName, + Parallel = true, + }); + + this.CommonAssertions(this.outputDirectory, "wav"); + } + + [TestMethod] + public async Task TestAudioCutterFormat() + { + await AudioCutter.Execute(new AudioCutter.Arguments + { + InputFile = this.testfile, + OutputDir = this.outputDirectory.FullName, + Parallel = true, + SegmentFileExtension = "mp3", + }); + + this.CommonAssertions(this.outputDirectory, "mp3"); + } + + [TestMethod] + public async Task TestAudioCutterOffsetsAndDuration() + { + await AudioCutter.Execute(new AudioCutter.Arguments + { + InputFile = this.testfile, + OutputDir = this.outputDirectory.FullName, + StartOffset = 150, + EndOffset = 300, + SegmentDuration = 90.0, + }); + + var files = this.outputDirectory.GetFiles(); + Assert.AreEqual(2, files.Length); + CollectionAssert.That.Contains(files, $"f969b39d-2705-42fc-992c-252a776f1af3_090705-0600_150-240.wav", FileInfoMapper); + CollectionAssert.That.Contains(files, $"f969b39d-2705-42fc-992c-252a776f1af3_090705-0600_240-300.wav", FileInfoMapper); + } + + [TestMethod] + public async Task TestAudioCutterSampleRate() + { + await AudioCutter.Execute(new AudioCutter.Arguments + { + InputFile = this.testfile, + OutputDir = this.outputDirectory.FullName, + SampleRate = 8000, + }); + + var files = this.CommonAssertions(this.outputDirectory, "wav"); + var info = TestHelper.GetAudioUtility().Info(files[0]); + Assert.AreEqual(8000, info.SampleRate); + } + + [TestMethod] + public async Task TestAudioCutterNoMixDown() + { + await AudioCutter.Execute(new AudioCutter.Arguments + { + InputFile = PathHelper.ResolveAssetPath("4channelsPureTones.wav"), + OutputDir = this.outputDirectory.FullName, + MixDownToMono = false, + StartOffset = 20, + EndOffset = 35, + SegmentDuration = 5, + }); + + var files = this.outputDirectory.GetFiles(); + Assert.AreEqual(3, files.Length); + CollectionAssert.That.Contains(files, $"4channelsPureTones_25-30.wav", FileInfoMapper); + var info = TestHelper.GetAudioUtility().Info(files[0]); + Assert.AreEqual(22050, info.SampleRate); + Assert.AreEqual(4, info.ChannelCount); + } + + [TestMethod] + public async Task TestAudioCutterMixDown() + { + await AudioCutter.Execute(new AudioCutter.Arguments + { + InputFile = PathHelper.ResolveAssetPath("4channelsPureTones.wav"), + OutputDir = this.outputDirectory.FullName, + MixDownToMono = true, + StartOffset = 20, + EndOffset = 35, + SegmentDuration = 5, + }); + + var files = this.outputDirectory.GetFiles(); + Assert.AreEqual(3, files.Length); + CollectionAssert.That.Contains(files, $"4channelsPureTones_25-30.wav", FileInfoMapper); + var info = TestHelper.GetAudioUtility().Info(files[0]); + Assert.AreEqual(22050, info.SampleRate); + Assert.AreEqual(1, info.ChannelCount); + } + + [TestMethod] + public async Task TestAudioCutterOverlap() + { + await AudioCutter.Execute(new AudioCutter.Arguments + { + InputFile = this.testfile, + OutputDir = this.outputDirectory.FullName, + SegmentOverlap = 30, + SegmentDuration = 60 * 4, + SegmentDurationMinimum = 130, + }); + + var files = this.outputDirectory.GetFiles(); + + // the last segment should be 120 seconds long but we set SegmentDurationMinimum to 130 so it should not be output + Assert.AreEqual(2, files.Length); + CollectionAssert.That.Contains(files, $"f969b39d-2705-42fc-992c-252a776f1af3_090705-0600_0-270.wav", FileInfoMapper); + CollectionAssert.That.Contains(files, $"f969b39d-2705-42fc-992c-252a776f1af3_090705-0600_240-510.wav", FileInfoMapper); + } + + private FileInfo[] CommonAssertions(DirectoryInfo targetDirectory, string expectedExtension) + { + var files = targetDirectory.GetFiles().ToArray(); + Assert.AreEqual(10, files.Length); + CollectionAssert.That.Contains(files, $"f969b39d-2705-42fc-992c-252a776f1af3_090705-0600_0-60.{expectedExtension}", FileInfoMapper); + CollectionAssert.That.Contains(files, $"f969b39d-2705-42fc-992c-252a776f1af3_090705-0600_540-600.{expectedExtension}", FileInfoMapper); + return files; + } + } +} diff --git a/tests/Acoustics.Test/AnalysisPrograms/SourcePreparers/LocalSourcePreparerTests.cs b/tests/Acoustics.Test/AnalysisPrograms/SourcePreparers/LocalSourcePreparerTests.cs index cc2c482a4..dbb2a1a08 100644 --- a/tests/Acoustics.Test/AnalysisPrograms/SourcePreparers/LocalSourcePreparerTests.cs +++ b/tests/Acoustics.Test/AnalysisPrograms/SourcePreparers/LocalSourcePreparerTests.cs @@ -105,10 +105,28 @@ public void ShouldSupportOverlap() } [TestMethod] - public void AbsoluteTimeAlignmentHasNoEffectWhenOffsetIsZero() + public void ShouldSupportMinimumSegmentFilter() { var source = TestHelper.AudioDetails[this.sourceFile.Name]; + var fileSegment = new FileSegment(this.sourceFile, source.SampleRate.Value, source.Duration.Value); + + this.preparer = new LocalSourcePreparer(filterShortSegments: true); + var analysisSegments = this.preparer.CalculateSegments(new[] { fileSegment }, this.settings).ToArray(); + + var expected = new[] + { + (0.0, 60.0).AsRange(), + (60.0, 120.0).AsRange(), + (120.0, 180.0).AsRange(), + (180.0, 240.0).AsRange(), + }; + AssertSegmentsAreEqual(analysisSegments, expected); + } + + [TestMethod] + public void AbsoluteTimeAlignmentHasNoEffectWhenOffsetIsZero() + { var newFile = this.testDirectory.CombineFile("4minute test_20161006-013000Z.mp3"); this.sourceFile.CopyTo(newFile.FullName); @@ -135,9 +153,7 @@ public void AbsoluteTimeAlignmentFailsWithoutDate() Assert.Throws( () => { - var fileSegment = new FileSegment(this.sourceFile, TimeAlignment.TrimBoth); - }); } @@ -264,6 +280,8 @@ public void ShouldSupportAbsoluteTimeAlignmentTrimEnd() private static void AssertSegmentsAreEqual(ISegment[] acutal, Range[] expected) { + Assert.AreEqual(acutal.Length, expected.Length, "The number of segments in actual and expected do not match"); + for (int i = 0; i < acutal.Length; i++) { var expectedStart = expected[i].Minimum; diff --git a/tests/Acoustics.Test/TestHelpers/Assertions.cs b/tests/Acoustics.Test/TestHelpers/Assertions.cs index e930281bd..2c94ffea9 100644 --- a/tests/Acoustics.Test/TestHelpers/Assertions.cs +++ b/tests/Acoustics.Test/TestHelpers/Assertions.cs @@ -172,6 +172,24 @@ public static void PathNotExists(this Assert assert, string path, string message $"Expected path {path} NOT to exist but it was found. {message}"); } + public static void Contains( + this CollectionAssert collectionAssert, + IEnumerable collection, + TResult expected, + Func mapper) + { + foreach (var item in collection) + { + var actual = mapper(item); + if (actual.Equals(expected)) + { + return; + } + } + + Assert.Fail($"Expected '{expected}' was not found in collection"); + } + public enum DiffStyle { Full, From b33f195f20759f07db3ee8bfc024adbdcd6a5a70 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Mon, 11 Jun 2018 15:36:00 +1000 Subject: [PATCH 3/3] Minor changes to build The clean target now completely removes all the content from the bin/debug directory. This is needed because unit tests that fail often do not get a chance to clean up after themselves. Also tweaked some resharper settings and added some clarifying notes about units in variables. --- AudioAnalysis.sln.DotSettings | 3 +++ CONTRIBUTING.md | 12 ++++++++++++ tests/Acoustics.Test/Acoustics.Test.csproj | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/AudioAnalysis.sln.DotSettings b/AudioAnalysis.sln.DotSettings index b2ce57e8d..29bc6dc36 100644 --- a/AudioAnalysis.sln.DotSettings +++ b/AudioAnalysis.sln.DotSettings @@ -1,9 +1,12 @@  False + DO_NOT_SHOW 15 + 1 NEVER True False + False <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> <TypePattern DisplayName="Non-reorderable types"> diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0af998a26..6708bfb46 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,18 @@ Best Practices - NEVER commit if the code does not build to the `master` branch - Try to work on branches if your code negatively affects production code - Write code in American English. Documentation may be written in Australian English. +- Wherever possible **use un-prefixed SI units for variables** + 1. Variables with no unit **MUST** be standard units + - `var duration = 30` should **always** be 30 seconds + - `var bandwidth = 50` should **always be hertz** + 1. **Never** use imperial units + 1. **Always** include the full unit in the name if it does not follow our standard + - avoid this if possible, see first point + - e.g. `var minKiloHertz = 3.5` + - e.g. `var limitHours = 6` + 1. **Do not** abbreviate units + 1. It is **recommended** that full units be used in any user facing field name + - e.g. `EventEndSeconds` in a CSV file Required Software ----------------- diff --git a/tests/Acoustics.Test/Acoustics.Test.csproj b/tests/Acoustics.Test/Acoustics.Test.csproj index 94ed4fbdc..3f235d96b 100644 --- a/tests/Acoustics.Test/Acoustics.Test.csproj +++ b/tests/Acoustics.Test/Acoustics.Test.csproj @@ -379,6 +379,10 @@ + + + +