Skip to content

Commit

Permalink
Merge pull request #170 from VPKSoft/add_errorTolerantSearch
Browse files Browse the repository at this point in the history
Add error-tolerant search (FuzzyWuzzy).
  • Loading branch information
Petteri Kautonen authored Dec 24, 2022
2 parents de60dd2 + e19e7aa commit f6b6b0f
Show file tree
Hide file tree
Showing 18 changed files with 233 additions and 29 deletions.
Binary file modified Help/amp-en/docs/img/img_linux/main_window1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Help/amp-en/docs/img/img_linux/settings5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Help/amp-en/docs/img/img_macos/settings5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Help/amp-en/docs/img/img_windows/settings5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions Help/amp-en/docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ Allows user to restore backed up application data from a zip file. The zip file

Before the backup is restored a following information is displayed: *"The application will shut down after the backup has been restored. Start the software again manually."*. After the backup has been restored a following message is displayed: *"Backup restore completed."*.

**Error-tolerant search**

Allows to use error-tolerant search for audio track filtering using *FuzzyWuzzy* algorithm.

The *Minimum tolerance* defines the filtering tolerance between 0 to 100. The *Maximum results* defines the amount of filtered results the filtering should return.

The *Always use error-tolerant search* defines if the *FuzzyWuzzy* algorithm should always be used for filtering, otherwise the algorithm is only used if the normal filtering yields no results.

*The Miscellaneous tab*

![image](img/settings5.png)
Binary file modified Help/amp-fi/docs/img/img_linux/settings5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Help/amp-fi/docs/img/img_macos/settings5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Help/amp-fi/docs/img/img_windows/settings5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions Help/amp-fi/docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ Antaa käyttäjän palauttaa sovelluksen tiedot zip-tiedostosta. Zip-tiedoston s

Ennen varmuuskopion palauttamista näytetään seuraava viesti: *"Sovellus sammutetaan varmuuskopion palauttamisen jälkeen. Käynnistä sovellus uudelleen manuaalisesti."*. Kun varmuuskopion palautus on valmis näytetään vielä viesti: *"Varmuuskopioinnin palautus valmis."*.

**Virhesietoinen haku**
Asettaa käyttöön virhesietoisen haun kappaleiden suodatukseen käyttäen *FuzzyWuzzy*-algoritmia.

*Minimitoleranssi* määrittää suodatuksen toleranssin välillä 0 - 100.

*Tulosjoukon koko* määrittää suodatettujen tuloksien maksimimäärän.
*Käytä aina virhesietoista hakua* määrittää, onko *FuzzyWuzzy*-algoritmi aina käytössä kappaleiden suodatukseen, muussa tapauksessa algoritmia käytetään ainoastaan, jos normaalisuodatus ei palauta yhtään tulosta.

*Sekalaista-välilehti*

![image](img/settings5.png)
56 changes: 43 additions & 13 deletions amp.EtoForms/ExtensionClasses/AudioTrackFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
using amp.EtoForms.Utilities;
using amp.Shared.Extensions;
using amp.Shared.Interfaces;
using FuzzierSharp;

namespace amp.EtoForms.ExtensionClasses;

Expand Down Expand Up @@ -123,24 +124,24 @@ internal static bool Match(this IAudioTrack audioTrack, string search)
}

search = search.ToUpper().Trim();
bool found1 = audioTrack.Artist?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.Album?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.Title?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.Year?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.Track?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.FileNameFull().IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.OverrideName?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
TrackDisplayNameGenerate.GetAudioTrackName(audioTrack).IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
MatchTagFindString(audioTrack.TagFindString, search);
var found1 = audioTrack.Artist?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.Album?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.Title?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.Year?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.Track?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.FileNameFull().IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
audioTrack.OverrideName?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
TrackDisplayNameGenerate.GetAudioTrackName(audioTrack).IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1 ||
MatchTagFindString(audioTrack.TagFindString, search);

string[] search2 = search.Split(' ');
if (search2.Length <= 1 || found1)
var search2 = search.Split(' ');
if (search2.Length <= 0 || found1)
{
return found1;
}

bool found2 = true;
foreach (string str in search2)
var found2 = true;
foreach (var str in search2)
{
var tmpStr = str.ToUpper();
found2 &= audioTrack.Artist?.IndexOf(tmpStr, StringComparison.InvariantCultureIgnoreCase) > -1 ||
Expand All @@ -157,6 +158,35 @@ internal static bool Match(this IAudioTrack audioTrack, string search)
return found2;
}

/// <summary>
/// Performs a FuzzyWuzzy partial string matching for the specified audio track.
/// </summary>
/// <param name="audioTrack">The audio track.</param>
/// <param name="search">The search string.</param>
/// <returns>The FuzzyWuzzy partial match score between <c>0</c> to <c>100</c>.</returns>
public static int FuzzyMatchScore(this IAudioTrack audioTrack, string search)
{
var searchStrings = new[]
{
audioTrack.Artist, audioTrack.Album, audioTrack.Title, audioTrack.Track, audioTrack.FileNameFull(),
audioTrack.OverrideName, TrackDisplayNameGenerate.GetAudioTrackName(audioTrack),
}.Where(f => f != null).Select(s => s!.Trim()).ToList();

return searchStrings.Select(f => Fuzz.PartialRatio(search.Trim(), f)).DefaultIfEmpty(0).Max();
}

/// <summary>
/// Performs a FuzzyWuzzy partial string matching for the specified audio track.
/// </summary>
/// <param name="audioTrack">The audio track.</param>
/// <param name="search">The search string.</param>
/// <param name="minimumRatio">The minimum ratio resulting a positive match.</param>
/// <returns><c>true</c> if the FuzzyWuzzy match was found, <c>false</c> otherwise.</returns>
internal static bool FuzzyStringMatch(this IAudioTrack audioTrack, string search, int minimumRatio = 70)
{
return audioTrack.FuzzyMatchScore(search) >= minimumRatio;
}

private static bool MatchTagFindString(string? value, string match)
{
if (value == null)
Expand Down
53 changes: 43 additions & 10 deletions amp.EtoForms/FormMain.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -721,23 +721,56 @@ private async void AudioTrackChanged(object? sender, AudioTrackChangedEventArgs
}
}

private async void QueryDivider_QueryCompleted(object? sender, QueryCompletedEventArgs<AlbumTrack> e)
private static ObservableCollection<AlbumTrack> FilterTracks(string? searchText, ObservableCollection<AlbumTrack> sourceTracks)
{
tracks = new ObservableCollection<AlbumTrack>(e.ResultList);

tracks = new ObservableCollection<AlbumTrack>(tracks.OrderBy(f => f.DisplayName));
var filtered = sourceTracks;

await Application.Instance.InvokeAsync(() =>
if (!string.IsNullOrWhiteSpace(searchText))
{
filteredTracks = tracks;
if (Globals.Settings.UseFuzzyWuzzySearch)
{
if (!Globals.Settings.FuzzyWuzzyAlwaysOn)
{
filtered =
new ObservableCollection<AlbumTrack>(sourceTracks
.Where(f => f.AudioTrack!.Match(searchText))
.OrderBy(f => f.DisplayName)
.ToList());
}

if (!string.IsNullOrWhiteSpace(tbSearch.Text))
if (filtered.Count == 0 || Globals.Settings.FuzzyWuzzyAlwaysOn)
{
filtered =
new ObservableCollection<AlbumTrack>(sourceTracks
.Where(f => f.AudioTrack!.FuzzyMatchScore(searchText) >= Globals.Settings.FuzzyWuzzyTolerance)
.OrderBy(f => f.AudioTrack!.FuzzyMatchScore(searchText))
.ThenBy(f => f.DisplayName)
.Take(Globals.Settings.FuzzyWuzzyMaxResults)
.ToList());
}
}
else
{
filteredTracks =
new ObservableCollection<AlbumTrack>(tracks
.Where(f => f.AudioTrack!.Match(tbSearch.Text))
filtered =
new ObservableCollection<AlbumTrack>(sourceTracks
.Where(f => f.AudioTrack!.Match(searchText))
.OrderBy(f => f.DisplayName)
.ToList());
}
}

return filtered;
}

private async void QueryDivider_QueryCompleted(object? sender, QueryCompletedEventArgs<AlbumTrack> e)
{
tracks = new ObservableCollection<AlbumTrack>(e.ResultList);

tracks = new ObservableCollection<AlbumTrack>(tracks.OrderBy(f => f.DisplayName));

await Application.Instance.InvokeAsync(() =>
{
filteredTracks = FilterTracks(tbSearch.Text, tracks);

gvAudioTracks.DataStore = filteredTracks;
lbLoadingText.Visible = false;
Expand Down
5 changes: 1 addition & 4 deletions amp.EtoForms/FormMain.Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,7 @@ private void FilterTracks(bool fromUserIdleEvent)
// These filters only apply when the user is active.
if (!userIdle)
{
if (!string.IsNullOrWhiteSpace(text))
{
filteredTracks = new ObservableCollection<AlbumTrack>(tracks.Where(f => f.AudioTrack!.Match(text)));
}
filteredTracks = FilterTracks(tbSearch.Text, tracks);
}

if (queueOnly)
Expand Down
38 changes: 37 additions & 1 deletion amp.EtoForms/Forms/FormSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ private void LoadSettings()
nsTitleMinimumLength.Value = Globals.Settings.TrackNamingMinimumTitleLength;
cbFallBackToFileNameIfNoLetters.Checked = Globals.Settings.TrackNamingFallbackToFileNameWhenNoLetters;
tbHelpFolder.Text = Globals.Settings.HelpFolder;

// FuzzyWuzzy search
cbFuzzyWuzzySearch.Checked = Globals.Settings.UseFuzzyWuzzySearch;
nsFuzzyWuzzySearchResults.Value = Globals.Settings.FuzzyWuzzyMaxResults;
nsFuzzyWuzzySearchTolerance.Value = Globals.Settings.FuzzyWuzzyTolerance;
cbFuzzyWuzzySearchAlwaysOn.Checked = Globals.Settings.FuzzyWuzzyAlwaysOn;
}

private void SaveSettings()
Expand Down Expand Up @@ -196,7 +202,6 @@ private void SaveSettings()
Globals.Settings.FftWindow = (int)value;
}

Globals.Settings.AudioVisualizationBars = cbAudioVisualizationBars.Checked == true;
Globals.Settings.AudioVisualizationBars = cbAudioVisualizationBars.Checked == true;
Globals.Settings.DisplayAudioLevels = cbVisualizeAudioLevels.Checked == true;
Globals.Settings.AudioLevelsHorizontal = cbLevelsVertical.Checked == true;
Expand All @@ -209,6 +214,12 @@ private void SaveSettings()

Globals.Settings.HelpFolder = tbHelpFolder.Text;

// FuzzyWuzzy search
Globals.Settings.UseFuzzyWuzzySearch = cbFuzzyWuzzySearch.Checked == true;
Globals.Settings.FuzzyWuzzyMaxResults = (int)nsFuzzyWuzzySearchResults.Value;
Globals.Settings.FuzzyWuzzyTolerance = (int)nsFuzzyWuzzySearchTolerance.Value;
Globals.Settings.FuzzyWuzzyAlwaysOn = cbFuzzyWuzzySearchAlwaysOn.Checked == true;

Globals.SaveSettings();
}

Expand Down Expand Up @@ -540,6 +551,17 @@ private void CreateMiscellaneousSettings()
},
Spacing = new Size(Globals.DefaultPadding, Globals.DefaultPadding),
},
cbFuzzyWuzzySearch,
new TableLayout
{
Rows =
{
new TableRow(new Label { Text = Shared.Localization.Settings.MinimumTolerance, }, nsFuzzyWuzzySearchTolerance, new TableCell(), new TableCell { ScaleWidth = true,}),
new TableRow(new Label { Text = Shared.Localization.Settings.MaximumResults, }, nsFuzzyWuzzySearchResults, new TableCell(), new TableCell { ScaleWidth = true,}),
new TableRow(new Label { Text = Shared.Localization.Settings.AlwaysUseErrorTolerantSearch, }, cbFuzzyWuzzySearchAlwaysOn, new TableCell(), new TableCell { ScaleWidth = true,}),
},
Spacing = new Size(Globals.DefaultPadding, Globals.DefaultPadding),
},
new TableRow { ScaleHeight = true, },
},
Spacing = new Size(Globals.DefaultPadding, Globals.DefaultPadding),
Expand All @@ -548,10 +570,18 @@ private void CreateMiscellaneousSettings()

btnBackupUserData.Click += BtnBackupUserData_Click;
btnRestoreUserData.Click += BtnRestoreUserData_Click;
cbFuzzyWuzzySearch.CheckedChanged += CbFuzzyWuzzySearch_CheckedChanged;

tbcSettings.Pages.Add(tabMiscellaneous);
}

private void CbFuzzyWuzzySearch_CheckedChanged(object? sender, EventArgs e)
{
cbFuzzyWuzzySearchAlwaysOn.Enabled = cbFuzzyWuzzySearch.Checked == true;
nsFuzzyWuzzySearchResults.Enabled = cbFuzzyWuzzySearch.Checked == true;
nsFuzzyWuzzySearchTolerance.Enabled = cbFuzzyWuzzySearch.Checked == true;
}

private void BtnRestoreUserData_Click(object? sender, EventArgs e)
{
if (MessageBox.Show(this, Messages.TheApplicationWillShutDownAfterTheBackupHasBeenRestoredStartTheSoftwareAgainManually, Messages.Information,
Expand Down Expand Up @@ -674,6 +704,11 @@ private void WeightedRandomDefaults()
private readonly Label lbQueueFinishActionSecond = new() { Text = UI.SecondActionWhenQueueIsFinished, };
private readonly ComboBox cmbQueueFinishActionSecond = new();

private readonly CheckBox cbFuzzyWuzzySearch = new() { Text = Shared.Localization.Settings.ErrorTolerantSearch, };
private readonly NumericStepper nsFuzzyWuzzySearchTolerance = new() { MinValue = 10, MaxValue = 100, Value = 70,};
private readonly NumericStepper nsFuzzyWuzzySearchResults = new() { MinValue = 10, MaxValue = 1000, Value = 50, };
private readonly CheckBox cbFuzzyWuzzySearchAlwaysOn = new();

private const string LocalizationActionPrefix = "QueueAction";

private readonly List<Pair<QueueFinishActionType, string>> dataStoreQueueFinishAction;
Expand All @@ -683,5 +718,6 @@ private void WeightedRandomDefaults()
private readonly Action backupResumeAction;
private readonly Button btnBackupUserData = new() { Text = UI.BackupApplicationData,};
private readonly Button btnRestoreUserData = new() { Text = UI.RestoreApplicationData,};

#endregion
}
31 changes: 31 additions & 0 deletions amp.EtoForms/Settings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,37 @@ public bool BiasedSkippedCountEnabled
/// <value>The first second finish action.</value>
[Settings(Default = QueueFinishActionType.None)]
public QueueFinishActionType QueueFinishActionSecond { get; set; }

#endregion

#region SearchSettings
/// <summary>
/// Gets or sets a value indicating whether use FuzzyWuzzy algorithm in the search.
/// </summary>
/// <value><c>true</c> if FuzzyWuzzy algorithm with the search; otherwise, <c>false</c>.</value>
[Settings(Default = false)]
public bool UseFuzzyWuzzySearch { get; set; }

/// <summary>
/// Gets or sets the FuzzyWuzzy search tolerance.
/// </summary>
/// <value>The FuzzyWuzzy search tolerance.</value>
[Settings(Default = 70)]
public int FuzzyWuzzyTolerance { get; set; }

/// <summary>
/// Gets or sets the FuzzyWuzzy maximum result amount.
/// </summary>
/// <value>The FuzzyWuzzy maximum result amount.</value>
[Settings(Default = 50)]
public int FuzzyWuzzyMaxResults { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to use FuzzyWuzzy algorithm always when searching for tracks.
/// </summary>
/// <value><c>true</c> if to use FuzzyWuzzy algorithm always; otherwise, <c>false</c>.</value>
[Settings(Default = false)]
public bool FuzzyWuzzyAlwaysOn { get; set; }
#endregion

#region Runtime
Expand Down
3 changes: 2 additions & 1 deletion amp.EtoForms/amp.EtoForms.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<DocumentationFile>bin\$(Configuration)\amp.EtoForms.xml</DocumentationFile>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<Version>1.0.1.3</Version>
<Version>1.0.2.0</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(IsOSX)'=='true'">
Expand Down Expand Up @@ -64,6 +64,7 @@
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="EtoForms.SpectrumVisualizer" Version="1.0.5" />
<PackageReference Include="FluentIcons.Resources" Version="1.0.1" />
<PackageReference Include="FuzzierSharp" Version="3.0.1" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.3" />
Expand Down
36 changes: 36 additions & 0 deletions amp.Shared/Localization/Settings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f6b6b0f

Please sign in to comment.