Skip to content

Commit 46a29a5

Browse files
committed
Merge branch 'develop'
* develop: We need to make sure we are shifting startsAt after range extraction. Hashes may not be ordered at the time of getting ranges or merging. Let's make sure we capture some details in case test fails intermittently. We actually need AVHashes to operate. Let's provide hashes in the result of ReadHashesByTrackId, in order to have a clearer picture regarding length of the fingerprints. Let's provide a method that will give us range of hashes from existing hashes. Adding the ability to read fingerprints by track ID. Update README.md Adding a test for consecutively repeating queries. Delete _config.yml
2 parents a3a95ae + edd76be commit 46a29a5

File tree

14 files changed

+254
-115
lines changed

14 files changed

+254
-115
lines changed

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ The default storage, which comes bundled with _soundfingerprinting_ NuGet packag
5656
docker run -d -v /persistent-dir:/app/data -p 3399:3399 -p 3340:3340 addictedcs/soundfingerprinting.emy:latest
5757

5858
**Emy** provides a backoffice interface which you can access on port :3340.
59-
In order to insert and query **Emy** server please install [SoundFingerprinting.Emy](https://www.nuget.org/packages/SoundFingerprinting.Emy) NuGet package.
59+
In order to insert and query **Emy** server please install [SoundFingerprinting.Emy][emy-nuget] NuGet package.
6060

6161
Install-Package SoundFingerprinting.Emy
6262

@@ -76,6 +76,8 @@ emyModelService.RegisterMatches(queryResult.ResultEntries);
7676
```
7777
Registering matches is now possible with <code>EmyModelService</code>. The results will be displayed in the **Emy** dashboard.
7878

79+
Similarly, [SoundFingerprinting.Emy][emy-nuget] provides `FFmpegAudioService`, which supports a wide variety of formats for both audio and video fingerprinting. More details about `FFmpegAudioService` can be found below.
80+
7981
<img src="https://i.imgur.com/lhqUY74.png" width="800">
8082

8183
If you plan to use **Emy** storage in a commercial project please contact sergiu@emysound.com for details. Enterprise version is ~12.5x faster when number of tracks exceeds ~10K, supports clustering, replication and much more. By using **Emy** you will also support core SoundFingerprinting library and its ongoing development.
@@ -85,6 +87,9 @@ Previous storages are now considered deprecate, as **Emy** is now considered the
8587
- ***Solr*** non-relational storage [soundfingerprinting.solr](https://github.com/AddictedCS/soundfingerprinting.solr). MIT licensed, useful when the number of tracks does not exceed 5000 tracks [deprecated].
8688
- ***MSSQL*** [soundfingerprinrint.sql](https://github.com/AddictedCS/soundfingerprinting.sql) [deprecated]. MIT licensed.
8789

90+
### Supported audio formats
91+
Read [Supported Audio Formats](https://github.com/AddictedCS/soundfingerprinting/wiki/Supported-Audio-Formats) page for details about different audio services and how you can use them in various operating systems.
92+
8893
### Query result details
8994
Every `ResultEntry` object will contain the following information:
9095
- `Track` - matched track from the datastore
@@ -117,11 +122,6 @@ Starting from version 5.1.0 the fingerprints signature has changed to be more re
117122
### Version 5.0.0
118123
Starting from version 5.0.0 _soundfingerprinting_ library supports .NET Standard 2.0. You can run the application not only on Window environment but on any other .NET Standard [compliant](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) runtime.
119124

120-
### List of additional soundfingerprinting integrations
121-
Default `SoundFingerprintingAudioService` supports only wave file at the input. If you would like to process other formats, consider using below extensions:
122-
- [SoundFingerprinting.Audio.NAudio](https://www.nuget.org/packages/SoundFingerprinting.Audio.NAudio) - replacement for default `SoundFingerprintingAudioService` audio service. Provides support for *.mp3* audio processing. Runs only on Windows as it uses [NAudio](https://github.com/naudio/NAudio) framework for underlying decoding and resampling.
123-
- [SoundFingerprinting.Audio.Bass](https://www.nuget.org/packages/SoundFingerprinting.Audio.Bass) - Bass.Net audio library integration, comes as a replacement for default service. Works faster than the default or NAudio, more accurate resampling, supports multiple audio formats (*.wav*, *.ogg*, *.mp3*, *.flac*). [Bass](http://www.un4seen.com) is free for non-comercial use. Recommended for enterprise users.
124-
- All demo apps are now located in separate git repositories, [duplicates detector](https://github.com/AddictedCS/soundfingerprinting.duplicatesdetector), [sound tools](https://github.com/AddictedCS/soundfingerprinting.soundtools).
125125

126126
### Algorithm configuration
127127
Fingerprinting and Querying algorithms can be easily parametrized with corresponding configuration objects passed as parameters on command creation.
@@ -198,4 +198,7 @@ If you want to contribute you are welcome to open issues or discuss on [issues](
198198
### License
199199
The framework is provided under [MIT](https://opensource.org/licenses/MIT) license agreement.
200200

201-
&copy; Soundfingerprinting, 2010-2019, sergiu@emysound.com
201+
&copy; Soundfingerprinting, 2010-2020, sergiu@emysound.com
202+
203+
204+
[emy-nuget]: https://www.nuget.org/packages/SoundFingerprinting.Emy

_config.yml

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/SoundFingerprinting.Tests/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
[assembly: AssemblyCulture("")]
1212
[assembly: ComVisible(false)]
1313
[assembly: Guid("4cac962e-ebc5-4006-a1e0-7ffb3e2483c2")]
14-
[assembly: AssemblyVersion("7.4.1.100")]
15-
[assembly: AssemblyInformationalVersion("7.4.1.100")]
14+
[assembly: AssemblyVersion("7.4.6.100")]
15+
[assembly: AssemblyInformationalVersion("7.4.6.100")]

src/SoundFingerprinting.Tests/Unit/Data/HashesTest.cs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,52 @@ public void CanSerializeAndDeserialize()
144144
}
145145
}
146146

147+
[Test]
148+
public void ShouldMergeNonOverlappingSequences()
149+
{
150+
var dtfi = CultureInfo.GetCultureInfo("en-US").DateTimeFormat;
151+
int count = 80;
152+
var aStartsAt = DateTime.Parse("01/15/2019 10:00:00", dtfi);
153+
var a = new Hashes(GetHashedFingerprints(count), count * 1.48f, aStartsAt);
154+
var bStartsAt = DateTime.Parse("01/15/2019 10:02:00", dtfi);
155+
var b = new Hashes(GetHashedFingerprints(count), count * 1.48f, bStartsAt);
156+
157+
Assert.IsTrue(a.MergeWith(b, out var c, 2 * 1.48f));
158+
Assert.AreEqual(count * 2, c.Count);
159+
160+
AssertInvariantsForHashes(c, aStartsAt);
161+
var rangeA = c.GetRange(aStartsAt, 120);
162+
AssertHashesAreEqual(a, rangeA);
163+
AssertInvariantsForHashes(rangeA, aStartsAt);
164+
var rangeB = c.GetRange(bStartsAt, 120);
165+
AssertHashesAreEqual(b, rangeB);
166+
AssertInvariantsForHashes(rangeB, bStartsAt);
167+
}
168+
169+
private static void AssertInvariantsForHashes(Hashes hashes, DateTime startsAt)
170+
{
171+
Assert.AreEqual(startsAt, hashes.RelativeTo);
172+
var list = hashes.ToList();
173+
Assert.AreEqual(0, list.First().StartsAt);
174+
Assert.AreEqual(0, list.First().SequenceNumber);
175+
for (int i = 1; i < hashes.Count; ++i)
176+
{
177+
Assert.IsTrue(list[i].StartsAt >= list[i - 1].StartsAt);
178+
Assert.IsTrue(list[i].SequenceNumber >= list[i - 1].SequenceNumber);
179+
}
180+
181+
Assert.AreEqual(hashes.DurationInSeconds, list.Last().StartsAt - list.First().StartsAt + 1.48f, 0.1f);
182+
}
183+
184+
private static void AssertHashesAreEqual(Hashes a, Hashes b)
185+
{
186+
Assert.AreEqual(a.Count, b.Count);
187+
foreach (var tuple in a.Zip(b))
188+
{
189+
CollectionAssert.AreEqual(tuple.First.HashBins, tuple.Second.HashBins);
190+
}
191+
}
192+
147193
private static Hashes Deserialize(byte[] buffer)
148194
{
149195
using var stream = new MemoryStream(buffer);
@@ -157,11 +203,11 @@ private static byte[] Serialize(Hashes timed)
157203
return stream.ToArray();
158204
}
159205

160-
private static List<HashedFingerprint> GetHashedFingerprints()
206+
private static List<HashedFingerprint> GetHashedFingerprints(int count = 100)
161207
{
162208
var random = new Random();
163209
var list = new List<HashedFingerprint>();
164-
for (int i = 0; i < 100; ++i)
210+
for (int i = 0; i < count; ++i)
165211
{
166212
int[] hashes = new int[25];
167213
for (int j = 0; j < 25; ++j)

src/SoundFingerprinting.Tests/Unit/Query/QueryCommandTest.cs

Lines changed: 61 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,39 @@ namespace SoundFingerprinting.Tests.Unit.Query
88
using SoundFingerprinting.Builder;
99
using SoundFingerprinting.Data;
1010
using SoundFingerprinting.InMemory;
11-
using SoundFingerprinting.Strides;
11+
using SoundFingerprinting.Query;
1212

1313
[TestFixture]
1414
public class QueryCommandTest
1515
{
16+
/**
17+
* query repeats consecutively two times in the track, without any pause
18+
* t -----10-----
19+
* track 111111011111
20+
* query 111111
21+
*/
22+
[Test]
23+
public async Task ShouldIdentifyConsecutiveRepeatingSequencesInTrack()
24+
{
25+
float[] match = TestUtilities.GenerateRandomFloatArray(10 * 5512);
26+
float[] twoCopies = new float[match.Length * 2];
27+
match.CopyTo(twoCopies, 0);
28+
match.CopyTo(twoCopies, match.Length);
29+
30+
var modelService = new InMemoryModelService();
31+
var audioService = new SoundFingerprintingAudioService();
32+
await InsertFingerprints(twoCopies, audioService, modelService);
33+
34+
var result = await GetQueryResult(match, audioService, modelService);
35+
36+
Assert.AreEqual(2, result.ResultEntries.Count());
37+
foreach (var entry in result.ResultEntries)
38+
{
39+
Assert.AreEqual(0.95, entry.Confidence, 0.05);
40+
Assert.AreEqual(10, entry.CoverageWithPermittedGapsLength, 1);
41+
}
42+
}
43+
1644
/**
1745
* Long queries, long matches
1846
* t -----15-----25-----35-----45-----
@@ -23,30 +51,13 @@ public class QueryCommandTest
2351
public async Task ShouldIdentifyOnlyOneMatch()
2452
{
2553
float[] match = TestUtilities.GenerateRandomFloatArray(10 * 5512);
26-
2754
float[] withJitter = AddJitter(match);
2855

2956
var modelService = new InMemoryModelService();
3057
var audioService = new SoundFingerprintingAudioService();
3158

32-
var hashes = await FingerprintCommandBuilder.Instance
33-
.BuildFingerprintCommand()
34-
.From(new AudioSamples(withJitter, "Queen", 5512))
35-
.UsingServices(audioService)
36-
.Hash();
37-
38-
modelService.Insert(new TrackInfo("123", "Bohemian Rhapsody", "Queen"), new Hashes(hashes, withJitter.Length / 5512f, DateTime.Now, Enumerable.Empty<string>()));
39-
40-
var result = await QueryCommandBuilder.Instance
41-
.BuildQueryCommand()
42-
.From(new AudioSamples(withJitter, "cnn", 5512))
43-
.WithQueryConfig(config =>
44-
{
45-
config.AllowMultipleMatchesOfTheSameTrackInQuery = true;
46-
return config;
47-
})
48-
.UsingServices(modelService, audioService)
49-
.Query();
59+
await InsertFingerprints(withJitter, audioService, modelService);
60+
var result = await GetQueryResult(withJitter, audioService, modelService);
5061

5162
Assert.IsTrue(result.ContainsMatches);
5263
var entries = result.ResultEntries.OrderBy(entry => entry.QueryMatchStartsAt).ToList();
@@ -71,28 +82,12 @@ public async Task ShouldIdentifyMultipleTracksInSameQuery()
7182
var modelService = new InMemoryModelService();
7283
var audioService = new SoundFingerprintingAudioService();
7384

74-
var hashes = await FingerprintCommandBuilder.Instance
75-
.BuildFingerprintCommand()
76-
.From(new AudioSamples(match, "Queen", 5512))
77-
.UsingServices(audioService)
78-
.Hash();
85+
await InsertFingerprints(match, audioService, modelService);
7986

80-
modelService.Insert(new TrackInfo("123", "Bohemian Rhapsody", "Queen"), new Hashes(hashes, match.Length / 5512f, DateTime.Now, Enumerable.Empty<string>()));
87+
var result = await GetQueryResult(withJitter, audioService, modelService);
8188

82-
var result = await QueryCommandBuilder.Instance
83-
.BuildQueryCommand()
84-
.From(new AudioSamples(withJitter, "cnn", 5512))
85-
.WithQueryConfig(config =>
86-
{
87-
config.AllowMultipleMatchesOfTheSameTrackInQuery = true;
88-
return config;
89-
})
90-
.UsingServices(modelService, audioService)
91-
.Query();
92-
9389
Assert.IsTrue(result.ContainsMatches);
9490
var entries = result.ResultEntries.OrderBy(entry => entry.QueryMatchStartsAt).ToList();
95-
9691
Assert.AreEqual(2, entries.Count);
9792
Assert.AreEqual(15d, entries[0].QueryMatchStartsAt, 1f);
9893
Assert.AreEqual(45d, entries[1].QueryMatchStartsAt, 1f);
@@ -113,25 +108,9 @@ public async Task ShouldIdentifyMultipleRegionsOfTheSameMatch()
113108
var modelService = new InMemoryModelService();
114109
var audioService = new SoundFingerprintingAudioService();
115110

116-
var hashes = await FingerprintCommandBuilder.Instance
117-
.BuildFingerprintCommand()
118-
.From(new AudioSamples(withJitter, "Queen", 5512))
119-
.UsingServices(audioService)
120-
.Hash();
121-
122-
modelService.Insert(new TrackInfo("123", "Bohemian Rhapsody", "Queen"), new Hashes(hashes, withJitter.Length / 5512f, DateTime.Now, Enumerable.Empty<string>()));
111+
await InsertFingerprints(withJitter, audioService, modelService);
123112

124-
var result = await QueryCommandBuilder.Instance
125-
.BuildQueryCommand()
126-
.From(new AudioSamples(match, "cnn", 5512))
127-
.WithQueryConfig(config =>
128-
{
129-
config.Stride = new IncrementalStaticStride(256);
130-
config.AllowMultipleMatchesOfTheSameTrackInQuery = true;
131-
return config;
132-
})
133-
.UsingServices(modelService, audioService)
134-
.Query();
113+
var result = await GetQueryResult(match, audioService, modelService);
135114

136115
Assert.IsTrue(result.ContainsMatches);
137116
var entries = result.ResultEntries.OrderBy(entry => entry.TrackMatchStartsAt).ToList();
@@ -151,7 +130,7 @@ public async Task ShouldIdentifyMultipleRegionsOfTheSameMatch()
151130
Assert.AreEqual(45d, entries[1].TrackMatchStartsAt, 1f);
152131
}
153132

154-
private float[] AddJitter(float[] match, int beforeSec = 15, int betweenSec = 10, int afterSec = 15)
133+
private static float[] AddJitter(float[] match, int beforeSec = 15, int betweenSec = 10, int afterSec = 15)
155134
{
156135
float[] before = TestUtilities.GenerateRandomFloatArray(beforeSec * 5512);
157136
float[] between = TestUtilities.GenerateRandomFloatArray(betweenSec * 5512);
@@ -165,5 +144,30 @@ private float[] AddJitter(float[] match, int beforeSec = 15, int betweenSec = 10
165144
Buffer.BlockCopy(after, 0, total, sizeof(float) * (before.Length + 2 * match.Length + between.Length), sizeof(float) * after.Length);
166145
return total;
167146
}
147+
148+
private static async Task<QueryResult> GetQueryResult(float[] match, IAudioService audioService, IModelService modelService)
149+
{
150+
return await QueryCommandBuilder.Instance
151+
.BuildQueryCommand()
152+
.From(new AudioSamples(match, "cnn", 5512))
153+
.WithQueryConfig(config =>
154+
{
155+
config.AllowMultipleMatchesOfTheSameTrackInQuery = true;
156+
return config;
157+
})
158+
.UsingServices(modelService, audioService)
159+
.Query();
160+
}
161+
162+
private static async Task InsertFingerprints(float[] audioSamples, IAudioService audioService, IModelService modelService)
163+
{
164+
var hashes = await FingerprintCommandBuilder.Instance
165+
.BuildFingerprintCommand()
166+
.From(new AudioSamples(audioSamples, "Queen", 5512))
167+
.UsingServices(audioService)
168+
.Hash();
169+
170+
modelService.Insert(new TrackInfo("123", "Bohemian Rhapsody", "Queen"), new Hashes(hashes, audioSamples.Length / 5512f, DateTime.Now, Enumerable.Empty<string>()));
171+
}
168172
}
169173
}

src/SoundFingerprinting.Tests/Unit/Query/RealtimeQueryCommandTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ await QueryCommandBuilder.Instance.BuildRealtimeQueryCommand()
262262

263263
Assert.AreEqual(hashes.Count, list.Select(entry => entry.Count).Sum());
264264
var merged = Hashes.Aggregate(list, 20d).ToList();
265-
Assert.AreEqual(2, merged.Count);
265+
Assert.AreEqual(2, merged.Count, $"Hashes:{string.Join(",", merged.Select(_ => $"{_.RelativeTo},{_.DurationInSeconds:0.00}"))}");
266266
Assert.AreEqual(hashes.Count, merged.Select(entry => entry.Count).Sum());
267267

268268
var aggregated = Hashes.Aggregate(list, double.MaxValue).ToList();

src/SoundFingerprinting/AdvancedModelService.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
namespace SoundFingerprinting
22
{
33
using System.Collections.Generic;
4-
using System.Linq;
5-
64
using SoundFingerprinting.DAO;
75
using SoundFingerprinting.DAO.Data;
8-
using SoundFingerprinting.Data;
96

107
public abstract class AdvancedModelService : ModelService, IAdvancedModelService
118
{
@@ -32,16 +29,5 @@ public virtual IEnumerable<SpectralImageData> GetSpectralImagesByTrackReference(
3229
{
3330
return spectralImageDao.GetSpectralImagesByTrackReference(trackReference);
3431
}
35-
36-
public IList<HashedFingerprint> ReadHashedFingerprintsByTrack(IModelReference trackReference)
37-
{
38-
return SubFingerprintDao.ReadHashedFingerprintsByTrackReference(trackReference)
39-
.Select(subFingerprint => new HashedFingerprint(
40-
subFingerprint.Hashes,
41-
subFingerprint.SequenceNumber,
42-
subFingerprint.SequenceAt,
43-
subFingerprint.OriginalPoint))
44-
.ToList();
45-
}
4632
}
4733
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace SoundFingerprinting.Data
2+
{
3+
using System;
4+
using ProtoBuf;
5+
6+
[Serializable]
7+
[ProtoContract(SkipConstructor = true)]
8+
public class AVHashes
9+
{
10+
public AVHashes(Hashes audioHashes, Hashes videoHashes)
11+
{
12+
AudioHashes = audioHashes;
13+
VideoHashes = videoHashes;
14+
}
15+
16+
[ProtoMember(1)]
17+
public Hashes AudioHashes { get; }
18+
19+
[ProtoMember(2)]
20+
public Hashes VideoHashes { get; }
21+
22+
public void Deconstruct(out Hashes audioHashes, out Hashes videoHashes)
23+
{
24+
audioHashes = AudioHashes;
25+
videoHashes = VideoHashes;
26+
}
27+
28+
public static AVHashes Empty => new AVHashes(Hashes.Empty, Hashes.Empty);
29+
30+
public override string ToString()
31+
{
32+
return $"Audio:[{AudioHashes}], Video:[{VideoHashes}]";
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)