Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add error-tolerant search (FuzzyWuzzy). #170

Merged
merged 4 commits into from
Dec 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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